1
0
forked from wrenn/wrenn

Add settings page, forgot/reset password flows, and me API client

Adds /dashboard/settings route with profile/password/OAuth/account-deletion
management. Adds /forgot-password and /reset-password routes. Enables sidebar
settings link. Adds typed me.ts API client.
This commit is contained in:
2026-04-16 03:25:03 +06:00
parent 93e6fe8160
commit e8a2217247
6 changed files with 969 additions and 9 deletions

View File

@ -0,0 +1,42 @@
import { apiFetch, type ApiResult } from '$lib/api/client';
import type { AuthResponse } from '$lib/api/auth';
export type MeResponse = {
name: string;
email: string;
has_password: boolean;
providers: string[];
};
export type ChangePasswordBody = {
current_password?: string;
new_password: string;
confirm_password?: string;
};
export const getMe = (): Promise<ApiResult<MeResponse>> =>
apiFetch('GET', '/api/v1/me');
export const updateName = (name: string): Promise<ApiResult<AuthResponse>> =>
apiFetch('PATCH', '/api/v1/me', { name });
export const changePassword = (body: ChangePasswordBody): Promise<ApiResult<void>> =>
apiFetch('POST', '/api/v1/me/password', body);
export const requestPasswordReset = (email: string): Promise<ApiResult<void>> =>
apiFetch('POST', '/api/v1/me/password/reset', { email });
export const confirmPasswordReset = (
token: string,
new_password: string
): Promise<ApiResult<void>> =>
apiFetch('POST', '/api/v1/me/password/reset/confirm', { token, new_password });
export const getProviderConnectURL = (provider: string): Promise<ApiResult<{ auth_url: string }>> =>
apiFetch('GET', `/api/v1/me/providers/${provider}/connect`);
export const disconnectProvider = (provider: string): Promise<ApiResult<void>> =>
apiFetch('DELETE', `/api/v1/me/providers/${provider}`);
export const deleteAccount = (confirmation: string): Promise<ApiResult<void>> =>
apiFetch('DELETE', '/api/v1/me', { confirmation });

View File

@ -280,13 +280,21 @@
<IconBell size={16} class="shrink-0" />
{#if !collapsed}<span class="text-ui">Notifications</span>{/if}
</div>
<div
class="flex cursor-not-allowed items-center rounded-[var(--radius-input)] px-2.5 py-2.5 opacity-35 {collapsed ? 'justify-center px-2' : 'gap-3'}"
title={collapsed ? 'Settings (coming soon)' : 'Coming soon'}
<a
href="/dashboard/settings"
class="group relative flex items-center rounded-[var(--radius-input)] px-2.5 py-2.5 transition-colors duration-150 hover:bg-[var(--color-bg-3)] {collapsed ? 'justify-center px-2' : 'gap-3'} {isActive('/dashboard/settings') ? (collapsed ? 'bg-[var(--color-accent-glow-mid)]' : 'bg-[var(--color-accent)]/[0.12]') : ''}"
title={collapsed ? 'Settings' : undefined}
>
<IconSettings size={16} class="shrink-0" />
{#if !collapsed}<span class="text-ui">Settings</span>{/if}
</div>
{#if isActive('/dashboard/settings') && !collapsed}
<div class="absolute left-0 top-1/2 h-6 w-1 -translate-y-1/2 rounded-r-full bg-[var(--color-accent)]"></div>
{/if}
<IconSettings size={16} class="shrink-0 {isActive('/dashboard/settings') ? 'text-[var(--color-accent-bright)]' : 'opacity-50 transition-opacity duration-150 group-hover:opacity-100'}" />
{#if !collapsed}
<span class="text-ui transition-colors duration-150 {isActive('/dashboard/settings') ? 'font-semibold text-[var(--color-accent-bright)]' : 'text-[var(--color-text-primary)] group-hover:text-[var(--color-text-bright)]'}">
Settings
</span>
{/if}
</a>
</div>
<!-- User footer -->