2023-07-23 15:08:43 +00:00
import { useState , useEffect , useMemo } from 'react'
2024-07-01 17:02:29 +00:00
import { gql , useQuery } from '@apollo/client'
2021-08-17 18:15:24 +00:00
import Comment , { CommentSkeleton } from './comment'
2022-07-21 22:55:05 +00:00
import Item from './item'
import ItemJob from './item-job'
2024-03-20 00:37:31 +00:00
import { NOTIFICATIONS } from '@/fragments/notifications'
2021-09-30 15:46:58 +00:00
import MoreFooter from './more-footer'
2022-01-19 21:02:38 +00:00
import Invite from './invite'
2024-03-20 00:37:31 +00:00
import { dayMonthYear , timeSince } from '@/lib/time'
2022-03-17 20:13:19 +00:00
import Link from 'next/link'
2024-03-20 00:37:31 +00:00
import Check from '@/svgs/check-double-line.svg'
import HandCoin from '@/svgs/hand-coin-fill.svg'
2024-07-11 00:23:05 +00:00
import UserAdd from '@/svgs/user-add-fill.svg'
2024-03-20 00:37:31 +00:00
import { LOST _BLURBS , FOUND _BLURBS , UNKNOWN _LINK _REL } from '@/lib/constants'
import CowboyHatIcon from '@/svgs/cowboy.svg'
import BaldIcon from '@/svgs/bald.svg'
2023-05-06 21:51:17 +00:00
import { RootProvider } from './root'
2023-07-24 18:35:05 +00:00
import Alert from 'react-bootstrap/Alert'
2023-06-12 18:03:44 +00:00
import styles from './notifications.module.css'
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
import { useServiceWorker } from './serviceworker'
import { Checkbox , Form } from './form'
2023-07-23 15:08:43 +00:00
import { useRouter } from 'next/router'
2023-08-06 18:04:25 +00:00
import { useData } from './use-data'
2024-03-20 00:37:31 +00:00
import { nostrZapDetails } from '@/lib/nostr'
2023-08-08 18:19:31 +00:00
import Text from './text'
2024-03-20 00:37:31 +00:00
import NostrIcon from '@/svgs/nostr.svg'
import { numWithUnits } from '@/lib/format'
import BountyIcon from '@/svgs/bounty-bag.svg'
2024-01-03 02:05:49 +00:00
import { LongCountdown } from './countdown'
2024-03-20 00:37:31 +00:00
import { nextBillingWithGrace } from '@/lib/territory'
import { commentSubTreeRootId } from '@/lib/item'
2024-04-15 21:22:26 +00:00
import LinkToContext from './link-to-context'
2024-07-01 17:02:29 +00:00
import { Badge , Button } from 'react-bootstrap'
import { useAct } from './item-act'
import { RETRY _PAID _ACTION } from '@/fragments/paidAction'
import { usePollVote } from './poll'
import { paidActionCacheMods } from './use-paid-mutation'
import { useRetryCreateItem } from './use-item-submit'
import { payBountyCacheMods } from './pay-bounty'
2024-07-09 18:10:41 +00:00
import { useToast } from './toast'
2021-08-17 18:15:24 +00:00
2023-08-06 15:47:58 +00:00
function Notification ( { n , fresh } ) {
const type = n . _ _typename
return (
2024-07-11 21:59:07 +00:00
< NotificationLayout nid = { nid ( n ) } type = { type } { ... defaultOnClick ( n ) } fresh = { fresh } >
2023-08-06 15:47:58 +00:00
{
( type === 'Earn' && < EarnNotification n = { n } / > ) ||
2023-11-21 23:32:22 +00:00
( type === 'Revenue' && < RevenueNotification n = { n } / > ) ||
2023-08-06 15:47:58 +00:00
( type === 'Invitification' && < Invitification n = { n } / > ) ||
2023-08-08 18:19:31 +00:00
( type === 'InvoicePaid' && ( n . invoice . nostr ? < NostrZap n = { n } / > : < InvoicePaid n = { n } / > ) ) ||
2024-03-25 20:20:11 +00:00
( type === 'WithdrawlPaid' && < WithdrawlPaid n = { n } / > ) ||
2023-08-06 15:47:58 +00:00
( type === 'Referral' && < Referral n = { n } / > ) ||
( type === 'Streak' && < Streak n = { n } / > ) ||
( type === 'Votification' && < Votification n = { n } / > ) ||
2023-09-12 15:31:46 +00:00
( type === 'ForwardedVotification' && < ForwardedVotification n = { n } / > ) ||
2023-08-06 15:47:58 +00:00
( type === 'Mention' && < Mention n = { n } / > ) ||
2024-06-03 17:12:42 +00:00
( type === 'ItemMention' && < ItemMention n = { n } / > ) ||
2023-08-06 15:47:58 +00:00
( type === 'JobChanged' && < JobChanged n = { n } / > ) ||
2023-08-29 01:27:56 +00:00
( type === 'Reply' && < Reply n = { n } / > ) ||
2024-01-03 02:05:49 +00:00
( type === 'SubStatus' && < SubStatus n = { n } / > ) ||
2024-01-11 17:27:54 +00:00
( type === 'FollowActivity' && < FollowActivity n = { n } / > ) ||
2024-03-05 19:56:02 +00:00
( type === 'TerritoryPost' && < TerritoryPost n = { n } / > ) ||
2024-05-19 20:52:02 +00:00
( type === 'TerritoryTransfer' && < TerritoryTransfer n = { n } / > ) ||
2024-05-28 17:18:54 +00:00
( type === 'Reminder' && < Reminder n = { n } / > ) ||
2024-07-11 00:23:05 +00:00
( type === 'Invoicification' && < Invoicification n = { n } / > ) ||
( type === 'ReferralReward' && < ReferralReward n = { n } / > )
2023-08-06 15:47:58 +00:00
}
< / N o t i f i c a t i o n L a y o u t >
)
2023-06-01 00:51:30 +00:00
}
2022-01-20 19:03:48 +00:00
2024-07-11 21:59:07 +00:00
function NotificationLayout ( { children , type , nid , href , as , fresh } ) {
2023-07-23 15:08:43 +00:00
const router = useRouter ( )
2024-07-11 21:59:07 +00:00
if ( ! href ) return < div className = { ` py-2 ${ fresh ? styles . fresh : '' } ` } > { children } < / d i v >
2023-06-01 00:51:30 +00:00
return (
2024-04-15 21:22:26 +00:00
< LinkToContext
2024-07-11 21:59:07 +00:00
className = { ` py-2 ${ type === 'Reply' ? styles . reply : '' } ${ fresh ? styles . fresh : '' } ${ router ? . query ? . nid === nid ? 'outline-it' : '' } ` }
2024-04-15 21:22:26 +00:00
onClick = { async ( e ) => {
e . preventDefault ( )
nid && await router . replace ( {
pathname : router . pathname ,
query : {
... router . query ,
nid
}
} , router . asPath , { ... router . options , shallow : true } )
router . push ( href , as )
} }
href = { href }
2023-06-01 18:22:39 +00:00
>
2023-06-01 00:51:30 +00:00
{ children }
2024-04-15 21:22:26 +00:00
< / L i n k T o C o n t e x t >
2023-02-01 14:44:35 +00:00
)
}
2024-07-11 21:59:07 +00:00
function NoteHeader ( { color , children , big } ) {
return (
2024-07-12 15:38:47 +00:00
< div className = { ` ${ styles . noteHeader } text- ${ color } ${ big ? '' : 'small' } pb-2 ` } >
2024-07-11 21:59:07 +00:00
{ children }
< / d i v >
)
}
2024-07-11 22:29:05 +00:00
function NoteItem ( { item } ) {
return (
< div >
{ item . title
? < Item item = { item } itemClassName = 'pt-0' / >
: (
< RootProvider root = { item . root } >
< Comment item = { item } noReply includeParent clickToContext / >
< / R o o t P r o v i d e r > ) }
< / d i v >
)
}
2023-07-23 15:08:43 +00:00
const defaultOnClick = n => {
2023-08-06 15:47:58 +00:00
const type = n . _ _typename
2023-08-30 15:22:25 +00:00
if ( type === 'Earn' ) {
let href = '/rewards/'
if ( n . minSortTime !== n . sortTime ) {
2023-11-09 00:15:36 +00:00
href += ` ${ dayMonthYear ( new Date ( n . minSortTime ) ) } / `
2023-08-30 15:22:25 +00:00
}
2023-11-09 00:15:36 +00:00
href += dayMonthYear ( new Date ( n . sortTime ) )
2023-08-30 15:22:25 +00:00
return { href }
}
2024-07-01 17:02:29 +00:00
const itemLink = item => {
if ( ! item ) return { }
if ( item . title ) {
return {
href : {
pathname : '/items/[id]' ,
query : { id : item . id }
} ,
as : ` /items/ ${ item . id } `
}
} else {
const rootId = commentSubTreeRootId ( item )
return {
href : {
pathname : '/items/[id]' ,
query : { id : rootId , commentId : item . id }
} ,
as : ` /items/ ${ rootId } `
}
}
}
2023-11-21 23:32:22 +00:00
if ( type === 'Revenue' ) return { href : ` /~ ${ n . subName } ` }
2024-01-03 02:05:49 +00:00
if ( type === 'SubStatus' ) return { href : ` /~ ${ n . sub . name } ` }
2023-08-06 15:47:58 +00:00
if ( type === 'Invitification' ) return { href : '/invites' }
if ( type === 'InvoicePaid' ) return { href : ` /invoices/ ${ n . invoice . id } ` }
2024-07-01 17:02:29 +00:00
if ( type === 'Invoicification' ) return itemLink ( n . invoice . item )
2024-03-26 01:09:28 +00:00
if ( type === 'WithdrawlPaid' ) return { href : ` /withdrawals/ ${ n . id } ` }
2023-08-06 15:47:58 +00:00
if ( type === 'Referral' ) return { href : '/referrals/month' }
2024-07-11 00:23:05 +00:00
if ( type === 'ReferralReward' ) return { href : '/referrals/month' }
2023-08-06 15:47:58 +00:00
if ( type === 'Streak' ) return { }
2024-03-05 19:56:02 +00:00
if ( type === 'TerritoryTransfer' ) return { href : ` /~ ${ n . sub . name } ` }
2023-08-06 15:47:58 +00:00
2024-05-28 17:18:54 +00:00
if ( ! n . item ) return { }
2023-08-06 15:47:58 +00:00
// Votification, Mention, JobChanged, Reply all have item
2024-07-01 17:02:29 +00:00
return itemLink ( n . item )
2023-06-01 00:51:30 +00:00
}
2023-02-01 14:44:35 +00:00
function Streak ( { n } ) {
function blurb ( n ) {
2023-10-04 23:20:52 +00:00
const index = Number ( n . id ) % Math . min ( FOUND _BLURBS . length , LOST _BLURBS . length )
2023-02-01 14:44:35 +00:00
if ( n . days ) {
2023-08-08 21:31:43 +00:00
return ` After ${ numWithUnits ( n . days , {
abbreviate : false ,
unitSingular : 'day' ,
unitPlural : 'days'
} ) } , ` + LOST_BLURBS[index]
2023-02-01 14:44:35 +00:00
}
return FOUND _BLURBS [ index ]
}
return (
2024-07-11 21:59:07 +00:00
< div className = 'd-flex' >
2023-02-01 14:44:35 +00:00
< div style = { { fontSize : '2rem' } } > { n . days ? < BaldIcon className = 'fill-grey' height = { 40 } width = { 40 } / > : < CowboyHatIcon className = 'fill-grey' height = { 40 } width = { 40 } / > } < / d i v >
2023-07-24 18:35:05 +00:00
< div className = 'ms-1 p-1' >
2023-11-21 23:32:22 +00:00
< span className = 'fw-bold' > you { n . days ? 'lost your' : 'found a' } cowboy hat < / s p a n >
2023-02-01 14:44:35 +00:00
< div > < small style = { { lineHeight : '140%' , display : 'inline-block' } } > { blurb ( n ) } < / s m a l l > < / d i v >
< / d i v >
2021-08-20 00:13:32 +00:00
< / d i v >
)
}
2023-06-01 18:22:39 +00:00
function EarnNotification ( { n } ) {
2023-08-30 00:13:21 +00:00
const time = n . minSortTime === n . sortTime ? dayMonthYear ( new Date ( n . minSortTime ) ) : ` ${ dayMonthYear ( new Date ( n . minSortTime ) ) } to ${ dayMonthYear ( new Date ( n . sortTime ) ) } `
2023-06-01 00:51:30 +00:00
return (
2024-07-11 21:59:07 +00:00
< div className = 'd-flex' >
2023-07-23 15:08:43 +00:00
< HandCoin className = 'align-self-center fill-boost mx-1' width = { 24 } height = { 24 } style = { { flex : '0 0 24px' , transform : 'rotateY(180deg)' } } / >
2024-07-11 21:59:07 +00:00
< div className = 'ms-2' >
< NoteHeader color = 'boost' big >
2023-08-30 00:13:21 +00:00
you stacked { numWithUnits ( n . earnedSats , { abbreviate : false } ) } in rewards < small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { time } < / s m a l l >
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2023-07-23 15:08:43 +00:00
{ n . sources &&
< div style = { { fontSize : '80%' , color : 'var(--theme-grey)' } } >
2023-08-08 21:04:06 +00:00
{ n . sources . posts > 0 && < span > { numWithUnits ( n . sources . posts , { abbreviate : false } ) } for top posts < / s p a n > }
{ n . sources . comments > 0 && < span > { n . sources . posts > 0 && ' \\ ' } { numWithUnits ( n . sources . comments , { abbreviate : false } ) } for top comments < / s p a n > }
{ n . sources . tipPosts > 0 && < span > { ( n . sources . comments > 0 || n . sources . posts > 0 ) && ' \\ ' } { numWithUnits ( n . sources . tipPosts , { abbreviate : false } ) } for zapping top posts early < / s p a n > }
{ n . sources . tipComments > 0 && < span > { ( n . sources . comments > 0 || n . sources . posts > 0 || n . sources . tipPosts > 0 ) && ' \\ ' } { numWithUnits ( n . sources . tipComments , { abbreviate : false } ) } for zapping top comments early < / s p a n > }
2023-07-23 15:08:43 +00:00
< / d i v > }
2023-08-30 15:00:47 +00:00
< div style = { { lineHeight : '140%' } } >
2024-07-11 00:23:05 +00:00
SN distributes the sats it earns to top stackers like you daily . The top stackers make the top posts and comments or zap the top posts and comments early and generously . View the rewards pool and make a donation < Link href = '/rewards' > here < / L i n k > .
< / d i v >
< small className = 'text-muted ms-1 pb-1 fw-normal' > click for details < / s m a l l >
< / d i v >
< / d i v >
)
}
function ReferralReward ( { n } ) {
return (
2024-07-11 21:59:07 +00:00
< div className = 'd-flex' >
2024-07-11 00:23:05 +00:00
< UserAdd className = 'align-self-center fill-success mx-1' width = { 24 } height = { 24 } style = { { flex : '0 0 24px' , transform : 'rotateY(180deg)' } } / >
2024-07-11 21:59:07 +00:00
< div className = 'ms-2' >
< NoteHeader color = 'success' big >
2024-07-11 00:23:05 +00:00
you stacked { numWithUnits ( n . earnedSats , { abbreviate : false } ) } in referral rewards < small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { dayMonthYear ( new Date ( n . sortTime ) ) } < / s m a l l >
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 00:23:05 +00:00
{ n . sources &&
< div style = { { fontSize : '80%' , color : 'var(--theme-grey)' } } >
{ n . sources . forever > 0 && < span > { numWithUnits ( n . sources . forever , { abbreviate : false } ) } for stackers joining because of you < / s p a n > }
2024-07-12 14:12:12 +00:00
{ n . sources . oneDay > 0 && < span > { n . sources . forever > 0 && ' \\ ' } { numWithUnits ( n . sources . oneDay , { abbreviate : false } ) } for stackers referred to content by you today < / s p a n > }
2024-07-11 00:23:05 +00:00
< / d i v > }
< div style = { { lineHeight : '140%' } } >
SN gives referral rewards to stackers like you for referring the top stackers daily . You refer stackers when they visit your posts , comments , profile , territory , or if they visit SN through your referral links .
2023-06-01 00:51:30 +00:00
< / d i v >
2023-08-30 15:00:47 +00:00
< small className = 'text-muted ms-1 pb-1 fw-normal' > click for details < / s m a l l >
2023-06-01 00:51:30 +00:00
< / d i v >
2023-07-23 15:08:43 +00:00
< / d i v >
2023-06-01 18:22:39 +00:00
)
2023-06-01 00:51:30 +00:00
}
2023-11-21 23:32:22 +00:00
function RevenueNotification ( { n } ) {
return (
2024-07-11 21:59:07 +00:00
< div className = 'd-flex' >
2023-11-21 23:32:22 +00:00
< BountyIcon className = 'align-self-center fill-success mx-1' width = { 24 } height = { 24 } style = { { flex : '0 0 24px' } } / >
2024-07-11 21:59:07 +00:00
< div className = ' pb-1' >
2023-11-21 23:32:22 +00:00
< div className = 'fw-bold text-success' >
you stacked { numWithUnits ( n . earnedSats , { abbreviate : false } ) } in territory revenue < small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
< / d i v >
< div style = { { lineHeight : '140%' } } >
As the founder of territory < Link href = { ` /~ ${ n . subName } ` } > ~ { n . subName } < / L i n k > , y o u r e c e i v e 5 0 % o f t h e r e v e n u e i t g e n e r a t e s a n d t h e o t h e r 5 0 % g o t o < L i n k h r e f = ' / r e w a r d s ' > r e w a r d s < / L i n k > .
< / d i v >
< / d i v >
< / d i v >
)
}
2024-01-03 02:05:49 +00:00
function SubStatus ( { n } ) {
const dueDate = nextBillingWithGrace ( n . sub )
return (
2024-07-11 21:59:07 +00:00
< div className = { ` fw-bold text- ${ n . sub . status === 'ACTIVE' ? 'success' : 'danger' } ` } >
2024-01-03 02:05:49 +00:00
{ n . sub . status === 'ACTIVE'
? 'your territory is active again'
: ( n . sub . status === 'GRACE'
? < > your territory payment for ~ { n . sub . name } is due or your territory will be archived in < LongCountdown date = { dueDate } / > < / >
: < > your territory ~ { n . sub . name } has been archived < / > ) }
< small className = 'text-muted d-block pb-1 fw-normal' > click to visit territory and pay < / s m a l l >
< / d i v >
)
}
2023-06-01 18:22:39 +00:00
function Invitification ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'secondary' >
2023-08-08 21:31:43 +00:00
your invite has been redeemed by
{ numWithUnits ( n . invite . invitees . length , {
abbreviate : false ,
unitSingular : 'stacker' ,
unitPlural : 'stackers'
} ) }
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2023-07-24 18:35:05 +00:00
< div className = 'ms-4 me-2 mt-1' >
2023-06-01 00:51:30 +00:00
< Invite
invite = { n . invite } active = {
! n . invite . revoked &&
! ( n . invite . limit && n . invite . invitees . length >= n . invite . limit )
}
/ >
< / d i v >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-08-08 18:19:31 +00:00
function NostrZap ( { n } ) {
const { nostr } = n . invoice
const { npub , content , note } = nostrZapDetails ( nostr )
return (
2024-07-11 21:59:07 +00:00
< div className = 'fw-bold text-nostr' >
< NostrIcon width = { 24 } height = { 24 } className = 'fill-nostr me-1' / > { numWithUnits ( n . earnedSats ) } zap from
{ // eslint-disable-next-line
2024-04-28 22:25:25 +00:00
< Link className = 'mx-1 text-reset text-underline' target = '_blank' href = { ` https://njump.me/ ${ npub } ` } rel = { UNKNOWN _LINK _REL } >
2023-08-08 18:19:31 +00:00
{ npub . slice ( 0 , 10 ) } ...
< / L i n k >
2024-03-05 01:20:14 +00:00
}
2024-07-11 21:59:07 +00:00
on { note
2023-08-08 18:19:31 +00:00
? (
2024-03-05 01:20:14 +00:00
// eslint-disable-next-line
2024-04-28 22:25:25 +00:00
< Link className = 'mx-1 text-reset text-underline' target = '_blank' href = { ` https://njump.me/ ${ note } ` } rel = { UNKNOWN _LINK _REL } >
2023-08-08 18:19:31 +00:00
{ note . slice ( 0 , 12 ) } ...
< / L i n k > )
: 'nostr' }
2024-07-11 21:59:07 +00:00
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
{ content && < small className = 'd-block ms-4 ps-1 mt-1 mb-1 text-muted fw-normal' > < Text > { content } < / T e x t > < / s m a l l > }
< / d i v >
2023-08-08 18:19:31 +00:00
)
}
2023-06-01 18:22:39 +00:00
function InvoicePaid ( { n } ) {
2023-10-03 19:35:53 +00:00
let payerSig
if ( n . invoice . lud18Data ) {
const { name , identifier , email , pubkey } = n . invoice . lud18Data
const id = identifier || email || pubkey
payerSig = '- '
if ( name ) {
payerSig += name
if ( id ) payerSig += ' \\ '
}
if ( id ) payerSig += id
}
2023-06-01 00:51:30 +00:00
return (
2024-07-11 21:59:07 +00:00
< div className = 'fw-bold text-info' >
2023-10-03 00:19:20 +00:00
< Check className = 'fill-info me-1' / > { numWithUnits ( n . earnedSats , { abbreviate : false , unitSingular : 'sat was' , unitPlural : 'sats were' } ) } deposited in your account
2023-08-06 15:47:58 +00:00
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
2023-10-03 19:35:53 +00:00
{ n . invoice . comment &&
< small className = 'd-block ms-4 ps-1 mt-1 mb-1 text-muted fw-normal' >
< Text > { n . invoice . comment } < / T e x t >
{ payerSig }
< / s m a l l > }
2023-08-06 15:47:58 +00:00
< / d i v >
2023-06-01 00:51:30 +00:00
)
}
2024-07-01 17:02:29 +00:00
function useActRetry ( { invoice } ) {
const bountyCacheMods = invoice . item ? . bounty ? payBountyCacheMods ( ) : { }
return useAct ( {
query : RETRY _PAID _ACTION ,
onPayError : ( e , cache , { data } ) => {
paidActionCacheMods ? . onPayError ? . ( e , cache , { data } )
bountyCacheMods ? . onPayError ? . ( e , cache , { data } )
} ,
onPaid : ( cache , { data } ) => {
paidActionCacheMods ? . onPaid ? . ( cache , { data } )
bountyCacheMods ? . onPaid ? . ( cache , { data } )
} ,
update : ( cache , { data } ) => {
const response = Object . values ( data ) [ 0 ]
if ( ! response ? . invoice ) return
cache . modify ( {
id : ` ItemAct: ${ invoice . itemAct ? . id } ` ,
fields : {
// this is a bit of a hack just to update the reference to the new invoice
invoice : ( ) => cache . writeFragment ( {
id : ` Invoice: ${ response . invoice . id } ` ,
fragment : gql `
fragment _ on Invoice {
bolt11
}
` ,
data : { bolt11 : response . invoice . bolt11 }
} )
}
} )
paidActionCacheMods ? . update ? . ( cache , { data } )
bountyCacheMods ? . update ? . ( cache , { data } )
}
} )
}
function Invoicification ( { n : { invoice , sortTime } } ) {
2024-07-09 18:10:41 +00:00
const toaster = useToast ( )
2024-07-01 17:02:29 +00:00
const actRetry = useActRetry ( { invoice } )
const retryCreateItem = useRetryCreateItem ( { id : invoice . item ? . id } )
const retryPollVote = usePollVote ( { query : RETRY _PAID _ACTION , itemId : invoice . item ? . id } )
// XXX if we navigate to an invoice after it is retried in notifications
// the cache will clear invoice.item and will error on window.back
// alternatively, we could/should
// 1. update the notification cache to include the new invoice
// 2. make item has-many invoices
if ( ! invoice . item ) return null
let retry
let actionString
let invoiceId
let invoiceActionState
const itemType = invoice . item . title ? 'post' : 'comment'
if ( invoice . actionType === 'ITEM_CREATE' ) {
actionString = ` ${ itemType } create `
retry = retryCreateItem ;
( { id : invoiceId , actionState : invoiceActionState } = invoice . item . invoice )
} else if ( invoice . actionType === 'POLL_VOTE' ) {
actionString = 'poll vote '
retry = retryPollVote
invoiceId = invoice . item . poll ? . meInvoiceId
invoiceActionState = invoice . item . poll ? . meInvoiceActionState
} else {
actionString = ` ${ invoice . actionType === 'ZAP'
? invoice . item . root ? . bounty ? 'bounty payment' : 'zap'
: 'downzap' } on $ { itemType } `
retry = actRetry ;
( { id : invoiceId , actionState : invoiceActionState } = invoice . itemAct . invoice )
}
2024-07-11 21:59:07 +00:00
let colorClass = 'info'
2024-07-01 17:02:29 +00:00
switch ( invoiceActionState ) {
case 'FAILED' :
actionString += 'failed'
2024-07-11 21:59:07 +00:00
colorClass = 'warning'
2024-07-01 17:02:29 +00:00
break
case 'PAID' :
actionString += 'paid'
2024-07-11 21:59:07 +00:00
colorClass = 'success'
2024-07-01 17:02:29 +00:00
break
default :
actionString += 'pending'
}
return (
2024-07-11 21:59:07 +00:00
< div >
< NoteHeader color = { colorClass } >
2024-07-01 17:02:29 +00:00
{ actionString }
< span className = 'ms-1 text-muted fw-light' > { numWithUnits ( invoice . satsRequested ) } < / s p a n >
< span className = { invoiceActionState === 'FAILED' ? 'visible' : 'invisible' } >
< Button
size = 'sm' variant = 'outline-warning ms-2 border-1 rounded py-0'
style = { { '--bs-btn-hover-color' : '#fff' , '--bs-btn-active-color' : '#fff' } }
2024-07-09 18:10:41 +00:00
onClick = { async ( ) => {
try {
const { error } = await retry ( { variables : { invoiceId : parseInt ( invoiceId ) } } )
if ( error ) throw error
} catch ( error ) {
toaster . danger ( error ? . message || error ? . toString ? . ( ) )
}
2024-07-01 17:02:29 +00:00
} }
>
retry
< / B u t t o n >
< span className = 'text-muted ms-2 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( sortTime ) ) } < / s p a n >
< / s p a n >
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { invoice . item } / >
2024-07-01 17:02:29 +00:00
< / d i v >
)
}
2024-03-25 20:20:11 +00:00
function WithdrawlPaid ( { n } ) {
return (
2024-07-11 21:59:07 +00:00
< div className = 'fw-bold text-info' >
2024-08-13 14:48:30 +00:00
< Check className = 'fill-info me-1' / > { numWithUnits ( n . earnedSats + n . withdrawl . satsFeePaid , { abbreviate : false , unitSingular : 'sat was ' , unitPlural : 'sats were ' } ) }
{ n . withdrawl . p2p || n . withdrawl . autoWithdraw ? 'sent to your attached wallet' : 'withdrawn from your account' }
2024-03-25 20:20:11 +00:00
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
2024-08-13 14:48:30 +00:00
{ ( n . withdrawl . p2p && < Badge className = { styles . badge } bg = { null } > p2p < / B a d g e > ) | |
( n . withdrawl . autoWithdraw && < Badge className = { styles . badge } bg = { null } > autowithdraw < / B a d g e > ) }
2024-03-25 20:20:11 +00:00
< / d i v >
)
}
2023-06-01 18:22:39 +00:00
function Referral ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2024-07-11 21:59:07 +00:00
< small className = 'fw-bold text-success' >
2024-07-11 00:23:05 +00:00
< UserAdd className = 'fill-success me-2' height = { 21 } width = { 21 } style = { { transform : 'rotateY(180deg)' } } / > someone joined SN because of you
2023-08-06 15:47:58 +00:00
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
< / s m a l l >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Votification ( { n } ) {
multiple forwards on a post (#403)
* multiple forwards on a post
first phase of the multi-forward support
* update the graphql mutation for discussion posts to accept and validate multiple forwards
* update the discussion form to allow multiple forwards in the UI
* start working on db schema changes
* uncomment db schema, add migration to create the new model, and update create_item, update_item
stored procedures
* Propagate updates from discussion to poll, link, and bounty forms
Update the create, update poll sql functions for multi forward support
* Update gql, typedefs, and resolver to return forwarded users in items responses
* UI changes to show multiple forward recipients, and conditional upvote logic changes
* Update notification text to reflect multiple forwards upon vote action
* Disallow duplicate stacker entries
* reduce duplication in populating adv-post-form initial values
* Update item_act sql function to implement multi-way forwarding
* Update referral functions to scale referral bonuses for forwarded users
* Update notification text to reflect non-100% forwarded sats cases
* Update wallet history sql queries to accommodate multi-forward use cases
* Block zaps for posts you are forwarded zaps at the API layer, in addition
to in the UI
* Delete fwdUserId column from Item table as part of migration
* Fix how we calculate stacked sats after partial forwards in wallet history
* Exclude entries from wallet history that are 0 stacked sats from posts with 100% forwarded to other users
* Fix wallet history query for forwarded stacked sats to be scaled by the fwd pct
* Reduce duplication in adv post form, and do some style tweaks for better layout
* Use MAX_FORWARDS constants
* Address various PR feedback
* first enhancement pass
* enhancement pass too
---------
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2023-08-23 22:44:17 +00:00
let forwardedSats = 0
let ForwardedUsers = null
if ( n . item . forwards ? . length ) {
forwardedSats = Math . floor ( n . earnedSats * n . item . forwards . map ( fwd => fwd . pct ) . reduce ( ( sum , cur ) => sum + cur ) / 100 )
ForwardedUsers = ( ) => n . item . forwards . map ( ( fwd , i ) =>
< span key = { fwd . user . name } >
< Link className = 'text-success' href = { ` / ${ fwd . user . name } ` } >
@ { fwd . user . name }
< / L i n k >
2023-08-28 14:59:01 +00:00
{ i !== n . item . forwards . length - 1 && ' ' }
multiple forwards on a post (#403)
* multiple forwards on a post
first phase of the multi-forward support
* update the graphql mutation for discussion posts to accept and validate multiple forwards
* update the discussion form to allow multiple forwards in the UI
* start working on db schema changes
* uncomment db schema, add migration to create the new model, and update create_item, update_item
stored procedures
* Propagate updates from discussion to poll, link, and bounty forms
Update the create, update poll sql functions for multi forward support
* Update gql, typedefs, and resolver to return forwarded users in items responses
* UI changes to show multiple forward recipients, and conditional upvote logic changes
* Update notification text to reflect multiple forwards upon vote action
* Disallow duplicate stacker entries
* reduce duplication in populating adv-post-form initial values
* Update item_act sql function to implement multi-way forwarding
* Update referral functions to scale referral bonuses for forwarded users
* Update notification text to reflect non-100% forwarded sats cases
* Update wallet history sql queries to accommodate multi-forward use cases
* Block zaps for posts you are forwarded zaps at the API layer, in addition
to in the UI
* Delete fwdUserId column from Item table as part of migration
* Fix how we calculate stacked sats after partial forwards in wallet history
* Exclude entries from wallet history that are 0 stacked sats from posts with 100% forwarded to other users
* Fix wallet history query for forwarded stacked sats to be scaled by the fwd pct
* Reduce duplication in adv post form, and do some style tweaks for better layout
* Use MAX_FORWARDS constants
* Address various PR feedback
* first enhancement pass
* enhancement pass too
---------
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2023-08-23 22:44:17 +00:00
< / s p a n > )
}
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'success' >
multiple forwards on a post (#403)
* multiple forwards on a post
first phase of the multi-forward support
* update the graphql mutation for discussion posts to accept and validate multiple forwards
* update the discussion form to allow multiple forwards in the UI
* start working on db schema changes
* uncomment db schema, add migration to create the new model, and update create_item, update_item
stored procedures
* Propagate updates from discussion to poll, link, and bounty forms
Update the create, update poll sql functions for multi forward support
* Update gql, typedefs, and resolver to return forwarded users in items responses
* UI changes to show multiple forward recipients, and conditional upvote logic changes
* Update notification text to reflect multiple forwards upon vote action
* Disallow duplicate stacker entries
* reduce duplication in populating adv-post-form initial values
* Update item_act sql function to implement multi-way forwarding
* Update referral functions to scale referral bonuses for forwarded users
* Update notification text to reflect non-100% forwarded sats cases
* Update wallet history sql queries to accommodate multi-forward use cases
* Block zaps for posts you are forwarded zaps at the API layer, in addition
to in the UI
* Delete fwdUserId column from Item table as part of migration
* Fix how we calculate stacked sats after partial forwards in wallet history
* Exclude entries from wallet history that are 0 stacked sats from posts with 100% forwarded to other users
* Fix wallet history query for forwarded stacked sats to be scaled by the fwd pct
* Reduce duplication in adv post form, and do some style tweaks for better layout
* Use MAX_FORWARDS constants
* Address various PR feedback
* first enhancement pass
* enhancement pass too
---------
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2023-08-23 22:44:17 +00:00
your { n . item . title ? 'post' : 'reply' } stacked { numWithUnits ( n . earnedSats , { abbreviate : false } ) }
{ n . item . forwards ? . length > 0 &&
< >
{ ' ' } and forwarded { numWithUnits ( forwardedSats , { abbreviate : false } ) } to { ' ' }
< ForwardedUsers / >
< / > }
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-09-12 15:31:46 +00:00
function ForwardedVotification ( { n } ) {
return (
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'success' >
2023-09-12 15:31:46 +00:00
you were forwarded { numWithUnits ( n . earnedSats , { abbreviate : false } ) } from
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2023-09-12 15:31:46 +00:00
< / >
)
}
2023-06-01 18:22:39 +00:00
function Mention ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'info' >
2023-06-01 00:51:30 +00:00
you were mentioned in
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2024-06-03 17:12:42 +00:00
function ItemMention ( { n } ) {
return (
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'info' >
2024-06-03 17:12:42 +00:00
your item was mentioned in
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2024-06-03 17:12:42 +00:00
< / >
)
}
2023-06-01 18:22:39 +00:00
function JobChanged ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = { n . item . status === 'ACTIVE' ? 'success' : 'boost' } >
2023-06-01 00:51:30 +00:00
{ n . item . status === 'ACTIVE'
? 'your job is active again'
: ( n . item . status === 'NOSATS'
? 'your job promotion ran out of sats'
: 'your job has been stopped' ) }
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2023-06-01 00:51:30 +00:00
< ItemJob item = { n . item } / >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Reply ( { n } ) {
2024-07-11 22:29:05 +00:00
return < NoteItem item = { n . item } / >
2023-06-01 00:51:30 +00:00
}
2023-08-29 01:27:56 +00:00
function FollowActivity ( { n } ) {
return (
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'info' >
2023-08-29 01:27:56 +00:00
a stacker you subscribe to { n . item . parentId ? 'commented' : 'posted' }
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2023-08-29 01:27:56 +00:00
< / >
)
}
2024-01-11 17:27:54 +00:00
function TerritoryPost ( { n } ) {
return (
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'info' >
2024-01-11 17:27:54 +00:00
new post in ~ { n . item . sub . name }
2024-07-11 21:59:07 +00:00
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< div >
2024-07-11 21:59:07 +00:00
< Item item = { n . item } itemClassName = 'pt-0' / >
2024-01-11 17:27:54 +00:00
< / d i v >
< / >
)
}
2024-03-05 19:56:02 +00:00
function TerritoryTransfer ( { n } ) {
return (
2024-07-11 22:29:05 +00:00
< div className = 'fw-bold text-info ' >
~ { n . sub . name } was transferred to you
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
< / d i v >
2024-03-05 19:56:02 +00:00
)
}
2024-05-19 20:52:02 +00:00
function Reminder ( { n } ) {
return (
< >
2024-07-11 21:59:07 +00:00
< NoteHeader color = 'info' >
you asked to be reminded of this { n . item . title ? 'post' : 'comment' }
< / N o t e H e a d e r >
2024-07-11 22:29:05 +00:00
< NoteItem item = { n . item } / >
2024-05-19 20:52:02 +00:00
< / >
)
}
2023-08-03 19:56:59 +00:00
export function NotificationAlert ( ) {
2023-06-12 18:03:44 +00:00
const [ showAlert , setShowAlert ] = useState ( false )
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const [ hasSubscription , setHasSubscription ] = useState ( false )
const [ error , setError ] = useState ( null )
2023-07-04 23:44:03 +00:00
const [ supported , setSupported ] = useState ( false )
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const sw = useServiceWorker ( )
2023-06-12 18:03:44 +00:00
useEffect ( ( ) => {
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const isSupported = sw . support . serviceWorker && sw . support . pushManager && sw . support . notification
2023-07-04 23:44:03 +00:00
if ( isSupported ) {
const isDefaultPermission = sw . permission . notification === 'default'
2023-07-25 14:14:45 +00:00
setShowAlert ( isDefaultPermission && ! window . localStorage . getItem ( 'hideNotifyPrompt' ) )
2023-07-04 23:44:03 +00:00
sw . registration ? . pushManager . getSubscription ( ) . then ( subscription => setHasSubscription ( ! ! subscription ) )
setSupported ( true )
}
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
} , [ sw ] )
2023-06-12 18:03:44 +00:00
const close = ( ) => {
2023-07-25 14:14:45 +00:00
window . localStorage . setItem ( 'hideNotifyPrompt' , 'yep' )
2023-06-12 18:03:44 +00:00
setShowAlert ( false )
}
return (
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
error
2023-06-12 18:03:44 +00:00
? (
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< Alert variant = 'danger' dismissible onClose = { ( ) => setError ( null ) } >
< span > { error . toString ( ) } < / s p a n >
2023-06-12 18:03:44 +00:00
< / A l e r t >
)
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
: showAlert
? (
< Alert variant = 'info' dismissible onClose = { close } >
< span className = 'align-middle' > Enable push notifications ? < / s p a n >
< button
className = { ` ${ styles . alertBtn } mx-1 ` }
onClick = { async ( ) => {
await sw . requestNotificationPermission ( )
. then ( close )
. catch ( setError )
} }
> Yes
< / b u t t o n >
2023-07-25 14:14:45 +00:00
< button className = { styles . alertBtn } onClick = { close } > No < / b u t t o n >
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< / A l e r t >
)
: (
2023-07-23 15:08:43 +00:00
< Form className = { ` d-flex justify-content-end ${ supported ? 'visible' : 'invisible' } ` } initial = { { pushNotify : hasSubscription } } >
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< Checkbox
2023-07-04 22:19:59 +00:00
name = 'pushNotify' label = { < span className = 'text-muted' > push notifications < / s p a n > }
2023-07-24 18:35:05 +00:00
groupClassName = { ` ${ styles . subFormGroup } mb-1 me-sm-3 me-0 ` }
2023-07-04 22:19:59 +00:00
inline checked = { hasSubscription } handleChange = { async ( ) => {
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
await sw . togglePushSubscription ( ) . catch ( setError )
} }
/ >
< / F o r m >
)
2023-06-12 18:03:44 +00:00
)
}
2023-08-04 00:14:04 +00:00
const nid = n => n . _ _typename + n . id + n . sortTime
2023-07-23 15:08:43 +00:00
export default function Notifications ( { ssrData } ) {
const { data , fetchMore } = useQuery ( NOTIFICATIONS )
2023-08-03 19:56:59 +00:00
const router = useRouter ( )
2023-08-06 18:04:25 +00:00
const dat = useData ( data , ssrData )
2023-07-23 15:08:43 +00:00
2024-04-06 23:28:23 +00:00
const { notifications , lastChecked , cursor } = useMemo ( ( ) => {
if ( ! dat ? . notifications ) return { }
// make sure we're using the oldest lastChecked we've seen
const retDat = { ... dat . notifications }
if ( ssrData ? . notifications ? . lastChecked < retDat . lastChecked ) {
retDat . lastChecked = ssrData . notifications . lastChecked
}
return retDat
2023-08-06 18:04:25 +00:00
} , [ dat ] )
2021-08-20 00:13:32 +00:00
2023-08-03 19:56:59 +00:00
useEffect ( ( ) => {
2023-08-06 15:47:58 +00:00
if ( lastChecked && ! router ? . query ? . checkedAt ) {
2023-08-03 23:04:43 +00:00
router . replace ( {
pathname : router . pathname ,
query : {
... router . query ,
nodata : true , // make sure nodata is set so we don't fetch on back/forward
checkedAt : lastChecked
}
2023-08-04 17:07:44 +00:00
} , router . asPath , { ... router . options , shallow : true } )
2023-08-03 19:56:59 +00:00
}
2024-04-04 23:09:42 +00:00
} , [ router ? . query ? . checkedAt , lastChecked ] )
2023-07-23 15:08:43 +00:00
2023-08-06 18:04:25 +00:00
if ( ! dat ) return < CommentsFlatSkeleton / >
2021-08-17 23:59:22 +00:00
2021-08-17 18:15:24 +00:00
return (
< >
2024-07-01 17:02:29 +00:00
{ notifications . map ( n =>
2023-08-06 15:47:58 +00:00
< Notification
n = { n } key = { nid ( n ) }
2024-04-04 23:09:42 +00:00
fresh = { new Date ( n . sortTime ) > new Date ( router ? . query ? . checkedAt ? ? lastChecked ) }
2023-08-06 15:47:58 +00:00
/ > ) }
2023-07-23 15:08:43 +00:00
< MoreFooter cursor = { cursor } count = { notifications ? . length } fetchMore = { fetchMore } Skeleton = { CommentsFlatSkeleton } noMoreText = 'NO MORE' / >
2021-08-17 18:15:24 +00:00
< / >
)
}
function CommentsFlatSkeleton ( ) {
const comments = new Array ( 21 ) . fill ( null )
return (
2023-07-23 15:08:43 +00:00
< div >
{ comments . map ( ( _ , i ) => (
< CommentSkeleton key = { i } skeletonChildren = { 0 } / >
) ) }
2021-08-17 18:15:24 +00:00
< / d i v >
)
}