wacrm/src/app/(dashboard)/settings/page.tsx
Arnas Donauskas 72a5e4cf70
feat(settings): user profile management
Adds a profile tab to /settings with the five things every B2B SaaS
user expects:

- Display name — edits profiles.full_name.
- Avatar — upload / replace / remove, stored in a new `avatars`
  Supabase Storage bucket with RLS scoped to the owner's folder.
  Migration 008_profile_avatars_storage.sql creates the bucket (public
  read, 2 MB cap, image mimes only) and the four storage.objects
  policies.
- Email — goes through supabase.auth.updateUser({ email }), which
  confirms to both the old and new addresses. UI surfaces a "check
  both inboxes" banner after save.
- Password — three-field form with a current-password gate implemented
  via a throwaway signInWithPassword() before calling updateUser.
- Active sessions — one-click "sign out of all devices" using
  supabase.auth.signOut({ scope: 'global' }), with a confirm dialog.

Plus read-only role, joined date, and user id for operator debugging.

The header avatar and sidebar user row are now both dropdown menus
linking to /settings?tab=profile and /settings?tab=whatsapp; the
settings page reads ?tab= to land on the right tab. useAuth gets a
refreshProfile() callback so the header/sidebar update immediately
after a save without a full reload.

Deferred (separate work): account deletion, 2FA, phone/timezone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 17:40:39 +03:00

108 lines
3.5 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Settings, MessageSquare, Tag, User } from 'lucide-react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { WhatsAppConfig } from '@/components/settings/whatsapp-config';
import { TemplateManager } from '@/components/settings/template-manager';
import { TagManager } from '@/components/settings/tag-manager';
import { ProfileForm } from '@/components/settings/profile-form';
import { PasswordForm } from '@/components/settings/password-form';
import { SessionsCard } from '@/components/settings/sessions-card';
const TAB_VALUES = ['profile', 'whatsapp', 'templates', 'tags'] as const;
type TabValue = (typeof TAB_VALUES)[number];
function isTabValue(v: string | null): v is TabValue {
return !!v && (TAB_VALUES as readonly string[]).includes(v);
}
export default function SettingsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const initialTab = (() => {
const q = searchParams.get('tab');
return isTabValue(q) ? q : 'profile';
})();
const [tab, setTab] = useState<TabValue>(initialTab);
// Keep the URL in sync with the active tab so the user-menu deep links
// work and the tab is bookmarkable / shareable.
useEffect(() => {
const q = searchParams.get('tab');
if (isTabValue(q) && q !== tab) setTab(q);
}, [searchParams, tab]);
const onChange = (next: TabValue) => {
setTab(next);
const params = new URLSearchParams(searchParams.toString());
params.set('tab', next);
router.replace(`/settings?${params.toString()}`, { scroll: false });
};
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold text-white">Settings</h1>
<p className="text-sm text-slate-400 mt-1">
Manage your profile, WhatsApp integration, message templates, and
tags.
</p>
</div>
<Tabs value={tab} onValueChange={(v) => onChange(v as TabValue)}>
<TabsList className="bg-slate-900 border border-slate-700">
<TabsTrigger
value="profile"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
>
<User className="size-4" />
Profile
</TabsTrigger>
<TabsTrigger
value="whatsapp"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
>
<Settings className="size-4" />
WhatsApp Config
</TabsTrigger>
<TabsTrigger
value="templates"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
>
<MessageSquare className="size-4" />
Templates
</TabsTrigger>
<TabsTrigger
value="tags"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
>
<Tag className="size-4" />
Tags
</TabsTrigger>
</TabsList>
<TabsContent value="profile" className="space-y-6">
<ProfileForm />
<PasswordForm />
<SessionsCard />
</TabsContent>
<TabsContent value="whatsapp">
<WhatsAppConfig />
</TabsContent>
<TabsContent value="templates">
<TemplateManager />
</TabsContent>
<TabsContent value="tags">
<TagManager />
</TabsContent>
</Tabs>
</div>
);
}