11import { useEffect , useState } from 'react'
2- import { toast } from 'sonner'
32import { api } from '@/api'
4- import { DocsLink } from '@/components/shared/DocsLink'
5- import { Button } from '@/components/ui/Button'
6- import { DisabledTooltip } from '@/components/ui/DisabledTooltip'
7- import { FieldDescription , FieldGroup , FieldLegend , FieldSet } from '@/components/ui/Field'
3+ import { FieldDescription , FieldLegend , FieldSet } from '@/components/ui/Field'
84import { Label } from '@/components/ui/Label'
95import { Loader } from '@/components/ui/Loader'
106import {
@@ -14,7 +10,6 @@ import {
1410 SelectTrigger ,
1511 SelectValue ,
1612} from '@/components/ui/Select'
17- import { Spinner } from '@/components/ui/Spinner'
1813import {
1914 COUNT_FIELDS ,
2015 type NotificationSubscriptions ,
@@ -54,12 +49,21 @@ const SubscriptionRow = ({ id, label, description, value, options, onChange }: R
5449 </ div >
5550)
5651
52+ export interface SubscriptionsDraft {
53+ draft : NotificationSubscriptions | null
54+ setField : ( key : keyof NotificationSubscriptions , value : number ) => void
55+ isDirty : boolean
56+ save : ( ) => Promise < unknown >
57+ isLoading : boolean
58+ isError : boolean
59+ }
60+
5761/**
5862 * Per-notification delivery preferences. These live behind their own endpoint
59- * rather than the user's settings object, so this tab keeps its own draft and
60- * Save button instead of joining the account form's submit .
63+ * rather than the user's settings object, so they're kept as a draft here and
64+ * saved alongside the account form when it submits .
6165 */
62- export const NotificationSubscriptionsSettings = ( { userId } : { userId : number } ) => {
66+ export const useNotificationSubscriptionsDraft = ( userId : number ) : SubscriptionsDraft => {
6367 const { data, isLoading, isError } = api . user . notificationSubscriptions ( userId )
6468 const updateMutation = api . user . useUpdateNotificationSubscriptions ( )
6569 const [ draft , setDraft ] = useState < NotificationSubscriptions | null > ( null )
@@ -77,6 +81,26 @@ export const NotificationSubscriptionsSettings = ({ userId }: { userId: number }
7781 [ ...SUBSCRIPTION_FIELDS , ...COUNT_FIELDS ] . some ( ( { key } ) => draft [ key ] !== saved [ key ] )
7882 )
7983
84+ return {
85+ draft,
86+ setField : ( key , value ) => setDraft ( ( prev ) => ( prev ? { ...prev , [ key ] : value } : prev ) ) ,
87+ isDirty,
88+ save : async ( ) => {
89+ if ( ! draft ) throw new Error ( 'Notification subscriptions have not loaded yet' )
90+ return updateMutation . mutateAsync ( { userId, subscriptions : draft } )
91+ } ,
92+ isLoading,
93+ isError,
94+ }
95+ }
96+
97+ export const NotificationSubscriptionFields = ( {
98+ subscriptions,
99+ } : {
100+ subscriptions : SubscriptionsDraft
101+ } ) => {
102+ const { draft, setField, isLoading, isError } = subscriptions
103+
80104 if ( isLoading ) return < Loader message = "Loading notification preferences..." />
81105 if ( isError || ! draft ) {
82106 return (
@@ -86,30 +110,10 @@ export const NotificationSubscriptionsSettings = ({ userId }: { userId: number }
86110 )
87111 }
88112
89- const setField = ( key : keyof NotificationSubscriptions , value : number ) =>
90- setDraft ( ( prev ) => ( prev ? { ...prev , [ key ] : value } : prev ) )
91-
92- const handleSave = async ( ) => {
93- try {
94- await updateMutation . mutateAsync ( { userId, subscriptions : draft } )
95- toast . success ( 'Notification preferences updated' )
96- } catch {
97- toast . error ( 'Failed to update notification preferences' )
98- }
99- }
100-
101113 return (
102- < FieldSet className = "min-h-0 flex-auto" >
103- < FieldLegend > Notification Subscriptions</ FieldLegend >
104- < FieldDescription >
105- Decide which MapRoulette notifications you would like to receive, along with whether you
106- would like to be sent an email informing you of the notification (either immediately or as a
107- daily digest).{ ' ' }
108- < DocsLink page = "notificationsAndEmail" icon = { null } >
109- Learn about notifications and email
110- </ DocsLink >
111- </ FieldDescription >
112- < FieldGroup className = "min-h-0 flex-auto overflow-y-auto" >
114+ < >
115+ < FieldSet >
116+ < FieldLegend variant = "label" > Notify me about</ FieldLegend >
113117 < div >
114118 { SUBSCRIPTION_FIELDS . map ( ( { key, label, description } ) => (
115119 < SubscriptionRow
@@ -123,47 +127,27 @@ export const NotificationSubscriptionsSettings = ({ userId }: { userId: number }
123127 />
124128 ) ) }
125129 </ div >
130+ </ FieldSet >
126131
127- < FieldSet >
128- < FieldLegend > Periodic summaries</ FieldLegend >
129- < FieldDescription >
130- These are sent on a schedule rather than when something happens.
131- </ FieldDescription >
132- < div >
133- { COUNT_FIELDS . map ( ( { key, label, description } ) => (
134- < SubscriptionRow
135- key = { key }
136- id = { `subscription-${ key } ` }
137- label = { label }
138- description = { description }
139- value = { draft [ key ] as number }
140- options = { subscriptionFrequencyOptions }
141- onChange = { ( value ) => setField ( key , value ) }
142- />
143- ) ) }
144- </ div >
145- </ FieldSet >
146- </ FieldGroup >
147-
148- < div className = "shrink-0 border-zinc-200 border-t pt-4 dark:border-slate-700" >
149- < DisabledTooltip show = { ! isDirty && ! updateMutation . isPending } message = "No changes to save" >
150- < Button
151- type = "button"
152- size = "lg"
153- onClick = { handleSave }
154- disabled = { updateMutation . isPending || ! isDirty }
155- >
156- { updateMutation . isPending ? (
157- < >
158- < Spinner />
159- Saving...
160- </ >
161- ) : (
162- 'Save Changes'
163- ) }
164- </ Button >
165- </ DisabledTooltip >
166- </ div >
167- </ FieldSet >
132+ < FieldSet >
133+ < FieldLegend variant = "label" > Periodic summaries</ FieldLegend >
134+ < FieldDescription >
135+ These are sent on a schedule rather than when something happens.
136+ </ FieldDescription >
137+ < div >
138+ { COUNT_FIELDS . map ( ( { key, label, description } ) => (
139+ < SubscriptionRow
140+ key = { key }
141+ id = { `subscription-${ key } ` }
142+ label = { label }
143+ description = { description }
144+ value = { draft [ key ] as number }
145+ options = { subscriptionFrequencyOptions }
146+ onChange = { ( value ) => setField ( key , value ) }
147+ />
148+ ) ) }
149+ </ div >
150+ </ FieldSet >
151+ </ >
168152 )
169153}
0 commit comments