indicate to user when there are new jobs
This commit is contained in:
parent
222335ca5e
commit
e2409efbaf
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}`
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "lastCheckedJobs" TIMESTAMP(3),
|
||||
ADD COLUMN "noteJobIndicator" BOOLEAN NOT NULL DEFAULT true;
|
|
@ -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])
|
||||
|
|
Loading…
Reference in New Issue