From f5815e1069b25a2bead15f0b6f504864b3be4032 Mon Sep 17 00:00:00 2001 From: isra el Date: Sat, 11 Jul 2026 07:54:26 +0300 Subject: [PATCH] feat: merge account into settings layout with billing/profile/security/support - account/layout.tsx: section header + route tabs (Billing & plan first, since the subscription is the most-visited account content), same RouteTabs interaction grammar as messaging and webhooks; active section survives refresh with shareable URLs - /dashboard/account redirects to /billing (SubscriptionInfo + plan CTAs); /profile, /security and /support are thin pages reusing the existing forms - Security combines password change with the delete-account flow in a clearly separated destructive "Danger zone" card - legacy routes (edit-profile, change-password, delete-account, get-support) redirect into the merged sections so old links keep working - account e2e: billing redirect + mocked subscription, security deep-link refresh survival, legacy redirects Build, 28 unit tests, and 16 e2e green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../(app)/dashboard/account/billing/page.tsx | 10 ++ .../account/change-password/page.tsx | 31 +----- .../dashboard/account/delete-account/page.tsx | 38 +------- .../dashboard/account/edit-profile/page.tsx | 31 +----- .../dashboard/account/get-support/page.tsx | 31 +----- web/app/(app)/dashboard/account/layout.tsx | 36 +++++++ web/app/(app)/dashboard/account/page.tsx | 95 +------------------ .../(app)/dashboard/account/profile/page.tsx | 24 +++++ .../(app)/dashboard/account/security/page.tsx | 50 ++++++++++ .../(app)/dashboard/account/support/page.tsx | 27 ++++++ web/e2e/account.spec.ts | 56 +++++++++++ 11 files changed, 223 insertions(+), 206 deletions(-) create mode 100644 web/app/(app)/dashboard/account/billing/page.tsx create mode 100644 web/app/(app)/dashboard/account/layout.tsx create mode 100644 web/app/(app)/dashboard/account/profile/page.tsx create mode 100644 web/app/(app)/dashboard/account/security/page.tsx create mode 100644 web/app/(app)/dashboard/account/support/page.tsx create mode 100644 web/e2e/account.spec.ts diff --git a/web/app/(app)/dashboard/account/billing/page.tsx b/web/app/(app)/dashboard/account/billing/page.tsx new file mode 100644 index 0000000..3f54cb7 --- /dev/null +++ b/web/app/(app)/dashboard/account/billing/page.tsx @@ -0,0 +1,10 @@ +import SubscriptionInfo from '../../(components)/subscription-info' + +// Billing & plan: current subscription, usage limits and plan CTAs. +export default function BillingPage() { + return ( +
+ +
+ ) +} diff --git a/web/app/(app)/dashboard/account/change-password/page.tsx b/web/app/(app)/dashboard/account/change-password/page.tsx index bcc558f..05a6e98 100644 --- a/web/app/(app)/dashboard/account/change-password/page.tsx +++ b/web/app/(app)/dashboard/account/change-password/page.tsx @@ -1,29 +1,6 @@ -import { ShieldIcon } from 'lucide-react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import ChangePasswordForm from '../../(components)/change-password-form' +import { redirect } from 'next/navigation' +// Legacy route: password management now lives in Account > Security. export default function ChangePasswordPage() { - return ( -
-
-
- -

Change Password

-
-

Update your account password

-
- -
- - - Password Security - Change your password to keep your account secure - - - - - -
-
- ) -} \ No newline at end of file + redirect('/dashboard/account/security') +} diff --git a/web/app/(app)/dashboard/account/delete-account/page.tsx b/web/app/(app)/dashboard/account/delete-account/page.tsx index bd8be6e..cb1c75a 100644 --- a/web/app/(app)/dashboard/account/delete-account/page.tsx +++ b/web/app/(app)/dashboard/account/delete-account/page.tsx @@ -1,34 +1,6 @@ -import { AlertTriangleIcon } from 'lucide-react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import DeleteAccountForm from '../../(components)/delete-account-form' +import { redirect } from 'next/navigation' -export default function DangerZonePage() { - return ( -
-
-
- -

Danger Zone

-
-

Manage critical account actions

-
- -
- - -
- - Delete Account -
- - Permanently delete your account and all associated data - -
- - - -
-
-
- ) -} \ No newline at end of file +// Legacy route: account deletion now lives in Account > Security (danger zone). +export default function DeleteAccountPage() { + redirect('/dashboard/account/security') +} diff --git a/web/app/(app)/dashboard/account/edit-profile/page.tsx b/web/app/(app)/dashboard/account/edit-profile/page.tsx index 76c6f7e..351a698 100644 --- a/web/app/(app)/dashboard/account/edit-profile/page.tsx +++ b/web/app/(app)/dashboard/account/edit-profile/page.tsx @@ -1,29 +1,6 @@ -import { UserIcon } from 'lucide-react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import EditProfileForm from '../../(components)/edit-profile-form' +import { redirect } from 'next/navigation' +// Legacy route: profile editing now lives in the merged account settings. export default function EditProfilePage() { - return ( -
-
-
- -

Edit Profile

-
-

Update your profile information

-
- -
- - - Profile Information - Update your personal details - - - - - -
-
- ) -} \ No newline at end of file + redirect('/dashboard/account/profile') +} diff --git a/web/app/(app)/dashboard/account/get-support/page.tsx b/web/app/(app)/dashboard/account/get-support/page.tsx index e40df96..a64cc7f 100644 --- a/web/app/(app)/dashboard/account/get-support/page.tsx +++ b/web/app/(app)/dashboard/account/get-support/page.tsx @@ -1,29 +1,6 @@ -import { MessageSquareIcon } from 'lucide-react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import SupportForm from '../../(components)/support-form' +import { redirect } from 'next/navigation' +// Legacy route: support now lives in Account > Support. export default function GetSupportPage() { - return ( -
-
-
- -

Get Support

-
-

Contact our support team for assistance

-
- -
- - - Contact Support - Fill out the form below and we'll get back to you as soon as possible. - - - - - -
-
- ) -} \ No newline at end of file + redirect('/dashboard/account/support') +} diff --git a/web/app/(app)/dashboard/account/layout.tsx b/web/app/(app)/dashboard/account/layout.tsx new file mode 100644 index 0000000..a0ca9af --- /dev/null +++ b/web/app/(app)/dashboard/account/layout.tsx @@ -0,0 +1,36 @@ +import type { PropsWithChildren } from 'react' +import { UserIcon } from 'lucide-react' +import RouteTabs from '@/components/shared/route-tabs' + +// Account is one settings experience: sections are route-based tabs (same +// interaction grammar as messaging/webhooks), Billing first since the +// subscription is the most-visited account content. +export default function AccountLayout({ children }: PropsWithChildren) { + return ( +
+
+
+ +

+ Account +

+
+

+ Manage your subscription, profile and security +

+
+ + + + {children} +
+ ) +} diff --git a/web/app/(app)/dashboard/account/page.tsx b/web/app/(app)/dashboard/account/page.tsx index 70c94de..ea89cdb 100644 --- a/web/app/(app)/dashboard/account/page.tsx +++ b/web/app/(app)/dashboard/account/page.tsx @@ -1,95 +1,6 @@ -import { UserIcon, PencilIcon, KeyIcon, AlertTriangleIcon, MessageSquareIcon } from 'lucide-react' -import SubscriptionInfo from '../(components)/subscription-info' -import { Button } from '@/components/ui/button' -import Link from 'next/link' +import { redirect } from 'next/navigation' +// Account is a settings section with subroutes; Billing is the default view. export default function AccountPage() { - return ( -
-
-
- -

Account

-
-

Manage your account settings and preferences

-
- -
- {/* Left column - Subscription Information */} -
-

Subscription Information

- -
- - {/* Right column - Account Management */} -
-
-

Account Management

- -
- - - - - - - - - - - -
-
- -
-

Danger Zone

- - - - -
-
-
-
- ) + redirect('/dashboard/account/billing') } diff --git a/web/app/(app)/dashboard/account/profile/page.tsx b/web/app/(app)/dashboard/account/profile/page.tsx new file mode 100644 index 0000000..354db2f --- /dev/null +++ b/web/app/(app)/dashboard/account/profile/page.tsx @@ -0,0 +1,24 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import EditProfileForm from '../../(components)/edit-profile-form' + +export default function ProfilePage() { + return ( +
+ + + Profile Information + Update your personal details + + + + + +
+ ) +} diff --git a/web/app/(app)/dashboard/account/security/page.tsx b/web/app/(app)/dashboard/account/security/page.tsx new file mode 100644 index 0000000..d4e2d5a --- /dev/null +++ b/web/app/(app)/dashboard/account/security/page.tsx @@ -0,0 +1,50 @@ +import { AlertTriangleIcon } from 'lucide-react' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import ChangePasswordForm from '../../(components)/change-password-form' +import DeleteAccountForm from '../../(components)/delete-account-form' + +// Security: password management, with account deletion in a clearly separated +// destructive card so the two actions can never be confused. +export default function SecurityPage() { + return ( +
+ + + Password + + Change your password to keep your account secure + + + + + + + +
+

+ Danger zone +

+ + +
+ + Delete Account +
+ + Permanently delete your account and all associated data + +
+ + + +
+
+
+ ) +} diff --git a/web/app/(app)/dashboard/account/support/page.tsx b/web/app/(app)/dashboard/account/support/page.tsx new file mode 100644 index 0000000..4b774c4 --- /dev/null +++ b/web/app/(app)/dashboard/account/support/page.tsx @@ -0,0 +1,27 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import SupportForm from '../../(components)/support-form' + +export default function SupportPage() { + return ( +
+ + + Contact Support + + Fill out the form below and we'll get back to you as soon as + possible. + + + + + + +
+ ) +} diff --git a/web/e2e/account.spec.ts b/web/e2e/account.spec.ts new file mode 100644 index 0000000..6ce5c3e --- /dev/null +++ b/web/e2e/account.spec.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test' +import { authenticate } from './session' +import { mockApi } from './mock-api' + +test.describe('account settings (mocked API, no real backend)', () => { + test('/dashboard/account redirects to billing and shows the subscription', async ({ + page, + context, + }) => { + await authenticate(context) + await mockApi(page) + await page.goto('/dashboard/account') + + await expect(page).toHaveURL(/\/dashboard\/account\/billing/) + const nav = page.getByRole('navigation', { name: 'Section navigation' }) + await expect( + nav.getByRole('link', { name: 'Billing & plan' }) + ).toHaveAttribute('aria-current', 'page') + // Mocked subscription plan name renders. + await expect(page.getByRole('heading', { name: 'Pro' })).toBeVisible() + }) + + test('security deep link survives refresh and separates the danger zone', async ({ + page, + context, + }) => { + await authenticate(context) + await mockApi(page) + await page.goto('/dashboard/account/security') + + const nav = page.getByRole('navigation', { name: 'Section navigation' }) + await expect(nav.getByRole('link', { name: 'Security' })).toHaveAttribute( + 'aria-current', + 'page' + ) + await expect(page.getByText('Password', { exact: true })).toBeVisible() + await expect(page.getByText('Danger zone')).toBeVisible() + await expect( + page.getByRole('heading', { name: 'Delete Account' }) + ).toBeVisible() + }) + + test('legacy routes redirect into the merged settings', async ({ + page, + context, + }) => { + await authenticate(context) + await mockApi(page) + + await page.goto('/dashboard/account/change-password') + await expect(page).toHaveURL(/\/dashboard\/account\/security/) + + await page.goto('/dashboard/account/edit-profile') + await expect(page).toHaveURL(/\/dashboard\/account\/profile/) + }) +})