Create base settings page

This commit is contained in:
MartinBraquet
2025-10-29 12:24:08 +01:00
parent 968845492f
commit 07d2a143a2
2 changed files with 176 additions and 150 deletions

View File

@@ -1,11 +1,6 @@
import {type Notification, NOTIFICATIONS_PER_PAGE,} from 'common/notifications'
import {PrivateUser, type User} from 'common/src/user'
import {
notification_destination_types,
notification_preference,
notification_preferences,
} from 'common/user-notification-preferences'
import {Fragment, useCallback, useEffect, useMemo, useState} from 'react'
import {type User} from 'common/src/user'
import {Fragment, useEffect, useMemo, useState} from 'react'
import {NoSEO} from 'web/components/NoSEO'
import {Col} from 'web/components/layout/col'
import {UncontrolledTabs} from 'web/components/layout/tabs'
@@ -17,9 +12,6 @@ import {Title} from 'web/components/widgets/title'
import {useGroupedNotifications} from 'web/hooks/use-notifications'
import {usePrivateUser, useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {MultiSelectAnswers} from 'web/components/answers/answer-compatibility-question-content'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
import {debounce} from 'lodash'
import {useRedirectIfSignedOut} from "web/hooks/use-redirect-if-signed-out";
export default function NotificationsPage() {
@@ -31,7 +23,6 @@ export default function NotificationsPage() {
<UncontrolledTabs
tabs={[
{title: 'Notifications', content: <NotificationsContent/>},
{title: 'Settings', content: <NotificationSettings/>},
]}
trackingName={'notifications page'}
/>
@@ -132,142 +123,3 @@ function RenderNotificationGroups(props: {
</>
)
}
const NotificationSettings = () => {
const privateUser = usePrivateUser()
if (!privateUser) return null
return <LoadedNotificationSettings privateUser={privateUser}/>
}
const LoadedNotificationSettings = (props: { privateUser: PrivateUser }) => {
const {privateUser} = props
const [prefs, setPrefs] =
usePersistentInMemoryState<notification_preferences>(
privateUser.notificationPreferences,
'notification-preferences'
)
const notificationTypes: {
type: notification_preference
question: string
}[] = [
{
type: 'new_match',
question:
'Where do you want to be notified when someone ... matches with you?',
},
{
type: 'new_message',
question: '... sends you a new message?',
},
{
type: 'new_profile_like',
question: '... likes your profile?',
},
{
type: 'new_endorsement',
question: '... endorses you?',
},
{
type: 'new_profile_ship',
question: '... ships you?',
},
{
type: 'tagged_user',
question: '... mentions you?',
},
{
type: 'on_new_follow',
question: '... follows you?',
},
{
type: 'new_search_alerts',
question: 'Alerts from bookmarked searches?',
},
{
type: 'opt_out_all',
question:
'Regardless, do you NEVER want to be notified via any of these channels?',
},
]
return (
<div className="mx-auto max-w-2xl">
<div className="flex flex-col gap-8 p-4">
{notificationTypes.map(({type, question}) => (
<NotificationOption
key={type}
type={type}
question={question}
selected={prefs[type]}
onUpdate={(selected) => {
setPrefs((prevPrefs) => ({...prevPrefs, [type]: selected}))
}}
/>
))}
</div>
</div>
)
}
const NotificationOption = (props: {
type: notification_preference
question: string
selected: notification_destination_types[]
onUpdate: (selected: notification_destination_types[]) => void
}) => {
const {type, question, selected, onUpdate} = props
const getSelectedValues = (destinations: string[]) => {
const values: number[] = []
if ((destinations ?? []).includes('email')) values.push(0)
if ((destinations ?? []).includes('browser')) values.push(1)
return values
}
const setValue = async (value: number[]) => {
const newDestinations: notification_destination_types[] = []
if (value.includes(0)) newDestinations.push('email')
if (value.includes(1)) newDestinations.push('browser')
onUpdate(newDestinations)
save(selected, newDestinations)
}
const save = useCallback(
debounce(
(
oldDestinations: notification_destination_types[],
newDestinations: notification_destination_types[]
) => {
// for each medium, if it changed, trigger a save
const mediums = ['email', 'browser'] as const
mediums.forEach((medium) => {
const wasEnabled = oldDestinations.includes(medium)
const isEnabled = newDestinations.includes(medium)
if (wasEnabled !== isEnabled) {
api('update-notif-settings', {
type,
medium,
enabled: isEnabled,
})
}
})
},
500
),
[]
)
return (
<div className="flex flex-col gap-2">
<div className="text-ink-700 font-medium">{question}</div>
<MultiSelectAnswers
options={['By email', 'On notifications page']}
values={getSelectedValues(selected)}
setValue={setValue}
/>
</div>
)
}

174
web/pages/settings.tsx Normal file
View File

@@ -0,0 +1,174 @@
import {PrivateUser} from 'common/src/user'
import {
notification_destination_types,
notification_preference,
notification_preferences,
} from 'common/user-notification-preferences'
import {useCallback} from 'react'
import {NoSEO} from 'web/components/NoSEO'
import {UncontrolledTabs} from 'web/components/layout/tabs'
import {PageBase} from 'web/components/page-base'
import {Title} from 'web/components/widgets/title'
import {usePrivateUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {MultiSelectAnswers} from 'web/components/answers/answer-compatibility-question-content'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
import {debounce} from 'lodash'
import {useRedirectIfSignedOut} from "web/hooks/use-redirect-if-signed-out";
export default function NotificationsPage() {
useRedirectIfSignedOut()
const privateUser = usePrivateUser()
if (!privateUser) return null
return (
<PageBase trackPageView={'settings page'} className={'mx-4'}>
<NoSEO/>
<Title>Settings</Title>
<UncontrolledTabs
tabs={[
{title: 'Account', content: <AccountSettings privateUser={privateUser}/>},
{title: 'Notifications', content: <NotificationSettings privateUser={privateUser}/>},
]}
trackingName={'settings page'}
/>
</PageBase>
)
}
const AccountSettings = (props: { privateUser: PrivateUser }) => {
const {privateUser} = props
return <></>
}
const NotificationSettings = (props: { privateUser: PrivateUser }) => {
const {privateUser} = props
const [prefs, setPrefs] =
usePersistentInMemoryState<notification_preferences>(
privateUser.notificationPreferences,
'notification-preferences'
)
const notificationTypes: {
type: notification_preference
question: string
}[] = [
{
type: 'new_match',
question:
'Where do you want to be notified when someone ... matches with you?',
},
{
type: 'new_message',
question: '... sends you a new message?',
},
{
type: 'new_profile_like',
question: '... likes your profile?',
},
{
type: 'new_endorsement',
question: '... endorses you?',
},
{
type: 'new_profile_ship',
question: '... ships you?',
},
{
type: 'tagged_user',
question: '... mentions you?',
},
{
type: 'on_new_follow',
question: '... follows you?',
},
{
type: 'new_search_alerts',
question: 'Alerts from bookmarked searches?',
},
{
type: 'opt_out_all',
question:
'Do you want to opt out of all notifications? (You can always change this later)?',
},
]
return (
<div className="mx-auto max-w-2xl">
<div className="flex flex-col gap-8 p-4">
{notificationTypes.map(({type, question}) => (
<NotificationOption
key={type}
type={type}
question={question}
selected={prefs[type]}
onUpdate={(selected) => {
setPrefs((prevPrefs) => ({...prevPrefs, [type]: selected}))
}}
/>
))}
</div>
</div>
)
}
const NotificationOption = (props: {
type: notification_preference
question: string
selected: notification_destination_types[]
onUpdate: (selected: notification_destination_types[]) => void
}) => {
const {type, question, selected, onUpdate} = props
const getSelectedValues = (destinations: string[]) => {
const values: number[] = []
if ((destinations ?? []).includes('email')) values.push(0)
if ((destinations ?? []).includes('browser')) values.push(1)
return values
}
const setValue = async (value: number[]) => {
const newDestinations: notification_destination_types[] = []
if (value.includes(0)) newDestinations.push('email')
if (value.includes(1)) newDestinations.push('browser')
onUpdate(newDestinations)
save(selected, newDestinations)
}
const save = useCallback(
debounce(
(
oldDestinations: notification_destination_types[],
newDestinations: notification_destination_types[]
) => {
// for each medium, if it changed, trigger a save
const mediums = ['email', 'browser'] as const
mediums.forEach((medium) => {
const wasEnabled = oldDestinations.includes(medium)
const isEnabled = newDestinations.includes(medium)
if (wasEnabled !== isEnabled) {
api('update-notif-settings', {
type,
medium,
enabled: isEnabled,
})
}
})
},
500
),
[]
)
return (
<div className="flex flex-col gap-2">
<div className="text-ink-700 font-medium">{question}</div>
<MultiSelectAnswers
options={['By email', 'On notifications page']}
values={getSelectedValues(selected)}
setValue={setValue}
/>
</div>
)
}