mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-28 21:06:40 -05:00
Create base settings page
This commit is contained in:
@@ -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
174
web/pages/settings.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user