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