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; | ||||
| @ -11,34 +11,35 @@ generator client { | ||||
| } | ||||
| 
 | ||||
| model User { | ||||
|   id             Int         @id @default(autoincrement()) | ||||
|   createdAt      DateTime    @default(now()) @map(name: "created_at") | ||||
|   updatedAt      DateTime    @default(now()) @updatedAt @map(name: "updated_at") | ||||
|   name           String?     @unique @db.Citext | ||||
|   email          String?     @unique | ||||
|   emailVerified  DateTime?   @map(name: "email_verified") | ||||
|   image          String? | ||||
|   items          Item[]      @relation("UserItems") | ||||
|   fwdItems       Item[]      @relation("FwdItem") | ||||
|   mentions       Mention[] | ||||
|   messages       Message[] | ||||
|   actions        ItemAct[] | ||||
|   invoices       Invoice[] | ||||
|   withdrawls     Withdrawl[] | ||||
|   invites        Invite[]    @relation(name: "Invites") | ||||
|   invite         Invite?     @relation(fields: [inviteId], references: [id]) | ||||
|   inviteId       String? | ||||
|   bio            Item?       @relation(fields: [bioId], references: [id]) | ||||
|   bioId          Int? | ||||
|   msats          Int         @default(0) | ||||
|   stackedMsats   Int         @default(0) | ||||
|   freeComments   Int         @default(5) | ||||
|   freePosts      Int         @default(2) | ||||
|   checkedNotesAt DateTime? | ||||
|   tipDefault     Int         @default(10) | ||||
|   pubkey         String?     @unique | ||||
|   trust          Float       @default(0) | ||||
|   lastSeenAt     DateTime? | ||||
|   id              Int         @id @default(autoincrement()) | ||||
|   createdAt       DateTime    @default(now()) @map(name: "created_at") | ||||
|   updatedAt       DateTime    @default(now()) @updatedAt @map(name: "updated_at") | ||||
|   name            String?     @unique @db.Citext | ||||
|   email           String?     @unique | ||||
|   emailVerified   DateTime?   @map(name: "email_verified") | ||||
|   image           String? | ||||
|   items           Item[]      @relation("UserItems") | ||||
|   fwdItems        Item[]      @relation("FwdItem") | ||||
|   mentions        Mention[] | ||||
|   messages        Message[] | ||||
|   actions         ItemAct[] | ||||
|   invoices        Invoice[] | ||||
|   withdrawls      Withdrawl[] | ||||
|   invites         Invite[]    @relation(name: "Invites") | ||||
|   invite          Invite?     @relation(fields: [inviteId], references: [id]) | ||||
|   inviteId        String? | ||||
|   bio             Item?       @relation(fields: [bioId], references: [id]) | ||||
|   bioId           Int? | ||||
|   msats           Int         @default(0) | ||||
|   stackedMsats    Int         @default(0) | ||||
|   freeComments    Int         @default(5) | ||||
|   freePosts       Int         @default(2) | ||||
|   checkedNotesAt  DateTime? | ||||
|   tipDefault      Int         @default(10) | ||||
|   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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user