indicate to user when there are new jobs

This commit is contained in:
keyan 2022-05-09 12:30:27 -05:00
parent 222335ca5e
commit e2409efbaf
9 changed files with 141 additions and 77 deletions

View File

@ -1,11 +1,34 @@
export default {
Query: {
sub: async (parent, { name }, { models }) => {
sub: async (parent, { name }, { models, me }) => {
if (me && name === 'jobs') {
models.user.update({
where: {
id: me.id
},
data: {
lastCheckedJobs: new Date()
}
}).catch(console.log)
}
return await models.sub.findUnique({
where: {
name
}
})
},
subLatestPost: async (parent, { name }, { models, me }) => {
const latest = await models.item.findFirst({
where: {
subName: name
},
orderBy: {
createdAt: 'desc'
}
})
return latest.createdAt
}
}
}

View File

@ -3,6 +3,7 @@ import { gql } from 'apollo-server-micro'
export default gql`
extend type Query {
sub(name: ID!): Sub
subLatestPost(name: ID!): String
}
type Sub {

View File

@ -29,7 +29,7 @@ export default gql`
setName(name: String!): Boolean
setSettings(tipDefault: Int!, noteItemSats: Boolean!, noteEarning: Boolean!,
noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
noteInvites:Boolean!): Boolean
noteInvites: Boolean!, noteJobIndicator: Boolean!): Boolean
upsertBio(bio: String!): User!
setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean
}
@ -57,5 +57,7 @@ export default gql`
noteMentions: Boolean!
noteDeposits: Boolean!
noteInvites: Boolean!
noteJobIndicator: Boolean!
lastCheckedJobs: String
}
`

View File

@ -13,6 +13,7 @@ import { useEffect, useState } from 'react'
import { randInRange } from '../lib/rand'
import { formatSats } from '../lib/format'
import NoteIcon from '../svgs/notification-4-fill.svg'
import { useQuery, gql } from '@apollo/client'
function WalletSummary ({ me }) {
if (!me) return null
@ -26,6 +27,23 @@ export default function Header ({ sub }) {
const [fired, setFired] = useState()
const me = useMe()
const prefix = sub ? `/~${sub}` : ''
const { data: subLatestPost } = useQuery(gql`
query subLatestPost($name: ID!) {
subLatestPost(name: $name)
}
`, { variables: { name: 'jobs' }, pollInterval: 600000, fetchPolicy: 'network-only' })
const [lastCheckedJobs, setLastCheckedJobs] = useState(new Date().getTime())
useEffect(() => {
if (me) {
setLastCheckedJobs(me.lastCheckedJobs)
} else {
if (sub === 'jobs') {
localStorage.setItem('lastCheckedJobs', new Date().getTime())
}
setLastCheckedJobs(localStorage.getItem('lastCheckedJobs'))
}
})
const Corner = () => {
if (me) {
@ -103,6 +121,43 @@ export default function Header ({ sub }) {
}
}
const NavItems = ({ className }) => {
return (
<>
<Nav.Item className={className}>
<Link href={prefix + '/recent'} passHref>
<Nav.Link className={styles.navLink}>recent</Nav.Link>
</Link>
</Nav.Item>
{!prefix &&
<Nav.Item className={className}>
<Link href='/top/posts/week' passHref>
<Nav.Link className={styles.navLink}>top</Nav.Link>
</Link>
</Nav.Item>}
<Nav.Item className={className}>
<div className='position-relative'>
<Link href='/~jobs' passHref>
<Nav.Link active={sub === 'jobs'} className={styles.navLink}>
jobs
</Nav.Link>
</Link>
{sub !== 'jobs' && (!me || me.noteJobIndicator) && (!lastCheckedJobs || lastCheckedJobs < subLatestPost?.subLatestPost) &&
<span className={styles.jobIndicator}>
<span className='invisible'>{' '}</span>
</span>}
</div>
</Nav.Item>
{me &&
<Nav.Item className={className}>
<Link href={prefix + '/post'} passHref>
<Nav.Link className={styles.navLinkButton}>post</Nav.Link>
</Link>
</Nav.Item>}
</>
)
}
return (
<>
<Container className='px-sm-0'>
@ -123,28 +178,7 @@ export default function Header ({ sub }) {
</Navbar.Brand>
</Link>
</div>
<Nav.Item className='d-none d-md-flex'>
<Link href={prefix + '/recent'} passHref>
<Nav.Link className={styles.navLink}>recent</Nav.Link>
</Link>
</Nav.Item>
{!prefix &&
<Nav.Item className='d-none d-md-flex'>
<Link href='/top/posts/week' passHref>
<Nav.Link className={styles.navLink}>top</Nav.Link>
</Link>
</Nav.Item>}
<Nav.Item className='d-none d-md-flex'>
<Link href='/~jobs' passHref>
<Nav.Link active={sub === 'jobs'} className={styles.navLink}>jobs</Nav.Link>
</Link>
</Nav.Item>
{me &&
<Nav.Item className='d-none d-md-flex'>
<Link href={prefix + '/post'} passHref>
<Nav.Link className={styles.navLinkButton}>post</Nav.Link>
</Link>
</Nav.Item>}
<NavItems className='d-none d-md-flex' />
<Nav.Item className={`text-monospace nav-link px-0 ${me?.name.length > 6 ? 'd-none d-lg-flex' : ''}`}>
<Price />
</Nav.Item>
@ -156,28 +190,7 @@ export default function Header ({ sub }) {
className={`${styles.navbarNav} justify-content-around`}
activeKey={path}
>
<Nav.Item>
<Link href={prefix + '/recent'} passHref>
<Nav.Link className={styles.navLink}>recent</Nav.Link>
</Link>
</Nav.Item>
{!prefix &&
<Nav.Item>
<Link href='/top/posts/week' passHref>
<Nav.Link className={styles.navLink}>top</Nav.Link>
</Link>
</Nav.Item>}
<Nav.Item>
<Link href='/~jobs' passHref>
<Nav.Link active={sub === 'jobs'} className={styles.navLink}>jobs</Nav.Link>
</Link>
</Nav.Item>
{me &&
<Nav.Item>
<Link href={prefix + '/post'} passHref>
<Nav.Link className={styles.navLinkButton}>post</Nav.Link>
</Link>
</Nav.Item>}
<NavItems />
</Nav>
</Navbar>
</Container>

View File

@ -27,6 +27,15 @@
fill: var(--theme-navLinkActive);
}
.jobIndicator {
position: absolute;
padding: .25rem;
background-color: var(--primary);
top: 3px;
right: 0px;
border: 1px solid var(--theme-body);
}
.notification {
position: absolute;
padding: .25rem;

View File

@ -23,6 +23,8 @@ export const ME = gql`
noteMentions
noteDeposits
noteInvites
noteJobIndicator
lastCheckedJobs
}
}`
@ -45,6 +47,8 @@ export const ME_SSR = gql`
noteMentions
noteDeposits
noteInvites
noteJobIndicator
lastCheckedJobs
}
}`

View File

@ -21,10 +21,11 @@ export default function Settings () {
gql`
mutation setSettings($tipDefault: Int!, $noteItemSats: Boolean!, $noteEarning: Boolean!,
$noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
$noteInvites: Boolean!) {
$noteInvites: Boolean!, $noteJobIndicator: Boolean!) {
setSettings(tipDefault: $tipDefault, noteItemSats: $noteItemSats,
noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites)
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
noteJobIndicator: $noteJobIndicator)
}`
)
@ -39,7 +40,8 @@ export default function Settings () {
noteAllDescendants: me?.noteAllDescendants,
noteMentions: me?.noteMentions,
noteDeposits: me?.noteDeposits,
noteInvites: me?.noteInvites
noteInvites: me?.noteInvites,
noteJobIndicator: me?.noteJobIndicator
}}
schema={SettingsSchema}
onSubmit={async ({ tipDefault, ...values }) => {
@ -84,6 +86,11 @@ export default function Settings () {
<Checkbox
label='someone mentions me'
name='noteMentions'
groupClassName='mb-0'
/>
<Checkbox
label='there is a new job'
name='noteJobIndicator'
/>
<div className='form-label'>saturday newsletter</div>
<Button href='https://mail.stacker.news/subscription/form' target='_blank'>(re)subscribe</Button>

View File

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "lastCheckedJobs" TIMESTAMP(3),
ADD COLUMN "noteJobIndicator" BOOLEAN NOT NULL DEFAULT true;

View File

@ -39,6 +39,7 @@ model User {
pubkey String? @unique
trust Float @default(0)
lastSeenAt DateTime?
lastCheckedJobs DateTime?
upvotePopover Boolean @default(false)
tipPopover Boolean @default(false)
@ -50,6 +51,7 @@ model User {
noteMentions Boolean @default(true)
noteDeposits Boolean @default(true)
noteInvites Boolean @default(true)
noteJobIndicator Boolean @default(true)
Earn Earn[]
@@index([createdAt])