Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 60 additions & 11 deletions apps/mail/app/(routes)/settings/security/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
import { m } from '@/paraglide/messages';
import { useForm } from 'react-hook-form';

import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { toast } from 'sonner';
import { useState, useEffect } from 'react';
import * as z from 'zod';

const formSchema = z.object({
Expand All @@ -23,6 +25,14 @@ const formSchema = z.object({

export default function SecurityPage() {
const [isSaving, setIsSaving] = useState(false);
const trpc = useTRPC();
const queryClient = useQueryClient();

Comment thread
suraj719 marked this conversation as resolved.
// Fetch current user settings
const { data: settingsData, isLoading } = useQuery(trpc.settings.get.queryOptions());
Comment thread
ahmetskilinc marked this conversation as resolved.
Outdated

// Save settings mutation
const { mutateAsync: saveUserSettings } = useMutation(trpc.settings.save.mutationOptions());

Comment thread
suraj719 marked this conversation as resolved.
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand All @@ -32,14 +42,45 @@ export default function SecurityPage() {
},
});

function onSubmit(values: z.infer<typeof formSchema>) {
// Update form when settings are loaded
useEffect(() => {
if (settingsData?.settings) {
form.reset({
twoFactorAuth: settingsData.settings.twoFactorAuth,
loginNotifications: settingsData.settings.loginNotifications,
});
}
}, [settingsData, form]);

Comment thread
suraj719 marked this conversation as resolved.
async function onSubmit(values: z.infer<typeof formSchema>) {
setIsSaving(true);
const saved = settingsData?.settings ? { ...settingsData.settings } : undefined;

try {
// Optimistically update the UI
queryClient.setQueryData(trpc.settings.get.queryKey(), (updater: any) => {
if (!updater) return;
return { settings: { ...updater.settings, ...values } };
});

Comment thread
suraj719 marked this conversation as resolved.
// TODO: Save settings in user's account
setTimeout(() => {
console.log(values);
await saveUserSettings({
twoFactorAuth: values.twoFactorAuth,
loginNotifications: values.loginNotifications,
});

toast.success(m['common.settings.saved']());
} catch (error) {
Comment thread
ahmetskilinc marked this conversation as resolved.
console.error('Failed to save security settings:', error);
toast.error(m['common.settings.failedToSave']());

// Revert optimistic update on error
queryClient.setQueryData(trpc.settings.get.queryKey(), (updater: any) => {
if (!updater) return;
return saved ? { settings: { ...updater.settings, ...saved } } : updater;
});
Comment thread
ahmetskilinc marked this conversation as resolved.
Outdated
} finally {
setIsSaving(false);
}, 1000);
}
}
Comment thread
suraj719 marked this conversation as resolved.

return (
Expand All @@ -50,7 +91,7 @@ export default function SecurityPage() {
footer={
<div className="flex gap-4">
<Button variant="destructive">{m['pages.settings.security.deleteAccount']()}</Button>
<Button type="submit" form="security-form" disabled={isSaving}>
<Button type="submit" form="security-form" disabled={isSaving || isLoading}>
{isSaving ? m['common.actions.saving']() : m['common.actions.saveChanges']()}
</Button>
Comment thread
suraj719 marked this conversation as resolved.
</div>
Expand All @@ -73,7 +114,11 @@ export default function SecurityPage() {
</FormDescription>
</div>
<FormControl className="ml-4">
<Switch checked={field.value} onCheckedChange={field.onChange} />
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
/>
Comment thread
ahmetskilinc marked this conversation as resolved.
</FormControl>
</FormItem>
)}
Expand All @@ -92,7 +137,11 @@ export default function SecurityPage() {
</FormDescription>
</div>
<FormControl className="ml-4">
<Switch checked={field.value} onCheckedChange={field.onChange} />
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isLoading}
/>
Comment thread
ahmetskilinc marked this conversation as resolved.
</FormControl>
</FormItem>
)}
Expand All @@ -103,4 +152,4 @@ export default function SecurityPage() {
</SettingsCard>
</div>
);
}
}
7 changes: 6 additions & 1 deletion apps/server/src/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export const userSettingsSchema = z.object({
imageCompression: z.enum(['low', 'medium', 'original']).default('medium'),
autoRead: z.boolean().default(true),
animations: z.boolean().default(false),
// Security settings
twoFactorAuth: z.boolean().default(false),
loginNotifications: z.boolean().default(true),
Comment thread
suraj719 marked this conversation as resolved.
});

export type UserSettings = z.infer<typeof userSettingsSchema>;
Expand All @@ -133,4 +136,6 @@ export const defaultUserSettings: UserSettings = {
undoSendEnabled: false,
imageCompression: 'medium',
animations: false,
};
twoFactorAuth: false,
loginNotifications: true,
};
Comment thread
suraj719 marked this conversation as resolved.