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 { export default {
Query: { 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({ return await models.sub.findUnique({
where: { where: {
name 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` export default gql`
extend type Query { extend type Query {
sub(name: ID!): Sub sub(name: ID!): Sub
subLatestPost(name: ID!): String
} }
type Sub { type Sub {

View File

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

View File

@ -13,6 +13,7 @@ import { useEffect, useState } from 'react'
import { randInRange } from '../lib/rand' import { randInRange } from '../lib/rand'
import { formatSats } from '../lib/format' import { formatSats } from '../lib/format'
import NoteIcon from '../svgs/notification-4-fill.svg' import NoteIcon from '../svgs/notification-4-fill.svg'
import { useQuery, gql } from '@apollo/client'
function WalletSummary ({ me }) { function WalletSummary ({ me }) {
if (!me) return null if (!me) return null
@ -26,6 +27,23 @@ export default function Header ({ sub }) {
const [fired, setFired] = useState() const [fired, setFired] = useState()
const me = useMe() const me = useMe()
const prefix = sub ? `/~${sub}` : '' 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 = () => { const Corner = () => {
if (me) { 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 ( return (
<> <>
<Container className='px-sm-0'> <Container className='px-sm-0'>
@ -123,28 +178,7 @@ export default function Header ({ sub }) {
</Navbar.Brand> </Navbar.Brand>
</Link> </Link>
</div> </div>
<Nav.Item className='d-none d-md-flex'> <NavItems 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>}
<Nav.Item className={`text-monospace nav-link px-0 ${me?.name.length > 6 ? 'd-none d-lg-flex' : ''}`}> <Nav.Item className={`text-monospace nav-link px-0 ${me?.name.length > 6 ? 'd-none d-lg-flex' : ''}`}>
<Price /> <Price />
</Nav.Item> </Nav.Item>
@ -156,28 +190,7 @@ export default function Header ({ sub }) {
className={`${styles.navbarNav} justify-content-around`} className={`${styles.navbarNav} justify-content-around`}
activeKey={path} activeKey={path}
> >
<Nav.Item> <NavItems />
<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>}
</Nav> </Nav>
</Navbar> </Navbar>
</Container> </Container>

View File

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

View File

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

View File

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

@ -11,34 +11,35 @@ generator client {
} }
model User { model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map(name: "created_at") createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at") updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
name String? @unique @db.Citext name String? @unique @db.Citext
email String? @unique email String? @unique
emailVerified DateTime? @map(name: "email_verified") emailVerified DateTime? @map(name: "email_verified")
image String? image String?
items Item[] @relation("UserItems") items Item[] @relation("UserItems")
fwdItems Item[] @relation("FwdItem") fwdItems Item[] @relation("FwdItem")
mentions Mention[] mentions Mention[]
messages Message[] messages Message[]
actions ItemAct[] actions ItemAct[]
invoices Invoice[] invoices Invoice[]
withdrawls Withdrawl[] withdrawls Withdrawl[]
invites Invite[] @relation(name: "Invites") invites Invite[] @relation(name: "Invites")
invite Invite? @relation(fields: [inviteId], references: [id]) invite Invite? @relation(fields: [inviteId], references: [id])
inviteId String? inviteId String?
bio Item? @relation(fields: [bioId], references: [id]) bio Item? @relation(fields: [bioId], references: [id])
bioId Int? bioId Int?
msats Int @default(0) msats Int @default(0)
stackedMsats Int @default(0) stackedMsats Int @default(0)
freeComments Int @default(5) freeComments Int @default(5)
freePosts Int @default(2) freePosts Int @default(2)
checkedNotesAt DateTime? checkedNotesAt DateTime?
tipDefault Int @default(10) tipDefault Int @default(10)
pubkey String? @unique pubkey String? @unique
trust Float @default(0) trust Float @default(0)
lastSeenAt DateTime? lastSeenAt DateTime?
lastCheckedJobs DateTime?
upvotePopover Boolean @default(false) upvotePopover Boolean @default(false)
tipPopover Boolean @default(false) tipPopover Boolean @default(false)
@ -50,6 +51,7 @@ model User {
noteMentions Boolean @default(true) noteMentions Boolean @default(true)
noteDeposits Boolean @default(true) noteDeposits Boolean @default(true)
noteInvites Boolean @default(true) noteInvites Boolean @default(true)
noteJobIndicator Boolean @default(true)
Earn Earn[] Earn Earn[]
@@index([createdAt]) @@index([createdAt])