diff --git a/src/lib/api/resources/users.ts b/src/lib/api/resources/users.ts new file mode 100644 index 00000000..c416cea6 --- /dev/null +++ b/src/lib/api/resources/users.ts @@ -0,0 +1,31 @@ +import type { FetchLike } from '../core' +import { put } from '../core' + +export interface UserUpdateParams { + picture?: string + element?: string + gender?: number + language?: string + theme?: string +} + +export interface UserResponse { + id: string + username: string + avatar: { + picture: string + element: string + } + gender: number + language: string + theme: string + role: number +} + +export const users = { + /** + * Update user settings + */ + update: (fetch: FetchLike, userId: string, params: UserUpdateParams) => + put(fetch, `/users/${userId}`, { user: params }) +} \ No newline at end of file diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index 2247542c..3cd6a15d 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -11,6 +11,7 @@ import { DropdownMenu } from 'bits-ui' import type { UserCookie } from '$lib/types/UserCookie' import { getAvatarSrc, getAvatarSrcSet } from '$lib/utils/avatar' + import UserSettingsModal from './UserSettingsModal.svelte' // Props from layout data const { @@ -83,6 +84,9 @@ isAuth && ($page.url.pathname === meHref || $page.url.pathname === localizeHref(`/${username}`)) ) + // Settings modal state + let settingsModalOpen = $state(false) + // Handle logout async function handleLogout() { try { @@ -187,8 +191,10 @@ - - {m.nav_settings()} + + {#if role !== null && role >= 7} Database @@ -218,6 +224,18 @@ /> + +{#if isAuth && account && currentUser} + (settingsModalOpen = open)} + {username} + userId={account.userId} + user={currentUser} + role={role ?? 0} + /> +{/if} + diff --git a/src/lib/types/UserCookie.d.ts b/src/lib/types/UserCookie.d.ts index b11e0f41..83a1df4f 100644 --- a/src/lib/types/UserCookie.d.ts +++ b/src/lib/types/UserCookie.d.ts @@ -4,4 +4,5 @@ export interface UserCookie { language: string gender: number theme: string + bahamut?: boolean } diff --git a/src/lib/utils/pictureData.ts b/src/lib/utils/pictureData.ts new file mode 100644 index 00000000..035b24b0 --- /dev/null +++ b/src/lib/utils/pictureData.ts @@ -0,0 +1,547 @@ +export interface Picture { + name: { + en: string + ja: string + } + filename: string + element: 'fire' | 'water' | 'earth' | 'wind' | 'light' | 'dark' +} + +export const pictureData: Picture[] = [ + { + name: { + en: 'Gran 2019', + ja: 'グラン' + }, + filename: 'gran_19', + element: 'water' + }, + { + name: { + en: 'Djeeta 2019', + ja: 'ジータ' + }, + filename: 'djeeta_19', + element: 'fire' + }, + { + name: { + en: 'Gran 2020', + ja: 'グラン' + }, + filename: 'gran_20', + element: 'water' + }, + { + name: { + en: 'Djeeta 2020', + ja: 'ジータ' + }, + filename: 'djeeta_20', + element: 'fire' + }, + { + name: { + en: 'Gran - Farer of the Skies', + ja: '空駆ける新鋭 グランver' + }, + filename: 'gran', + element: 'water' + }, + { + name: { + en: 'Djeeta - Farer of the Skies', + ja: '空駆ける新鋭 ジータver' + }, + filename: 'djeeta', + element: 'fire' + }, + { + name: { + en: 'Vyrn', + ja: 'ビィ' + }, + filename: 'vyrn', + element: 'wind' + }, + { + name: { + en: 'Lyria', + ja: 'ルリア' + }, + filename: 'lyria', + element: 'water' + }, + { + name: { + en: 'Katalina', + ja: 'カタリナ' + }, + filename: 'katalina', + element: 'water' + }, + { + name: { + en: 'Rackam', + ja: 'ラカム' + }, + filename: 'rackam', + element: 'fire' + }, + { + name: { + en: 'Io', + ja: 'イオ' + }, + filename: 'io', + element: 'light' + }, + { + name: { + en: 'Eugen', + ja: 'オイゲン' + }, + filename: 'eugen', + element: 'earth' + }, + { + name: { + en: 'Rosetta', + ja: 'ロゼッタ' + }, + filename: 'rosetta', + element: 'wind' + }, + { + name: { + en: 'Ferry', + ja: 'フェリ' + }, + filename: 'ferry', + element: 'light' + }, + { + name: { + en: 'Lecia', + ja: 'リーシャ' + }, + filename: 'lecia', + element: 'wind' + }, + { + name: { + en: 'Monika', + ja: 'モニカ' + }, + filename: 'monika', + element: 'wind' + }, + { + name: { + en: 'Sturm', + ja: 'スツルム' + }, + filename: 'sturm', + element: 'fire' + }, + { + name: { + en: 'Drang', + ja: 'ドランク' + }, + filename: 'drang', + element: 'water' + }, + { + name: { + en: 'Orchid', + ja: 'オーキス' + }, + filename: 'orchid', + element: 'dark' + }, + { + name: { + en: 'Black Knight', + ja: '黒騎士' + }, + filename: 'black-knight', + element: 'dark' + }, + { + name: { + en: 'Noa', + ja: 'ノア' + }, + filename: 'noa', + element: 'light' + }, + { + name: { + en: 'Rein', + ja: 'ラインハルザ' + }, + filename: 'rein', + element: 'earth' + }, + { + name: { + en: 'Cain', + ja: 'カイン' + }, + filename: 'cain', + element: 'earth' + }, + { + name: { + en: 'Pholia', + ja: 'フォリア' + }, + filename: 'pholia', + element: 'water' + }, + { + name: { + en: 'Alliah', + ja: 'アリアちゃん' + }, + filename: 'alliah', + element: 'earth' + }, + { + name: { + en: 'Sandalphon', + ja: 'サンダルフォン' + }, + filename: 'sandalphon', + element: 'light' + }, + { + name: { + en: 'Cagliostro', + ja: 'カリオストロ' + }, + filename: 'cagliostro', + element: 'earth' + }, + { + name: { + en: 'Clarisse', + ja: 'クラリス' + }, + filename: 'clarisse', + element: 'fire' + }, + { + name: { + en: 'Vira', + ja: 'ヴィーラ' + }, + filename: 'vira', + element: 'earth' + }, + { + name: { + en: 'Percival', + ja: 'パーシヴァル' + }, + filename: 'percival', + element: 'fire' + }, + { + name: { + en: 'Siegfried', + ja: 'ジークフリート' + }, + filename: 'siegfried', + element: 'earth' + }, + { + name: { + en: 'Lancelot', + ja: 'ランスロット' + }, + filename: 'lancelot', + element: 'water' + }, + { + name: { + en: 'Vane', + ja: 'ヴェイン' + }, + filename: 'vane', + element: 'water' + }, + { + name: { + en: 'Yuel', + ja: 'ユエル' + }, + filename: 'yuel', + element: 'fire' + }, + { + name: { + en: 'Societte', + ja: 'ソシエ' + }, + filename: 'societte', + element: 'water' + }, + { + name: { + en: 'Anila', + ja: 'アニラ' + }, + filename: 'anila', + element: 'fire' + }, + { + name: { + en: 'Andira', + ja: 'アンチラ' + }, + filename: 'andira', + element: 'wind' + }, + { + name: { + en: 'Mahira', + ja: 'マキラ' + }, + filename: 'mahira', + element: 'earth' + }, + { + name: { + en: 'Vajra', + ja: 'ヴァジラ' + }, + filename: 'vajra', + element: 'water' + }, + { + name: { + en: 'Kumbhira', + ja: 'クビラ' + }, + filename: 'kumbhira', + element: 'light' + }, + { + name: { + en: 'Vikala', + ja: 'ビカラ' + }, + filename: 'vikala', + element: 'dark' + }, + { + name: { + en: 'Catura', + ja: 'シャトラ' + }, + filename: 'catura', + element: 'wind' + }, + { + name: { + en: 'Seox', + ja: 'シス' + }, + filename: 'seox', + element: 'dark' + }, + { + name: { + en: 'Seofon', + ja: 'シエテ' + }, + filename: 'seofon', + element: 'wind' + }, + { + name: { + en: 'Tweyen', + ja: 'ソーン' + }, + filename: 'tweyen', + element: 'light' + }, + { + name: { + en: 'Threo', + ja: 'サラーサ' + }, + filename: 'threo', + element: 'earth' + }, + { + name: { + en: 'Feower', + ja: 'カトル' + }, + filename: 'feower', + element: 'water' + }, + { + name: { + en: 'Fif', + ja: 'フュンフ' + }, + filename: 'fif', + element: 'light' + }, + { + name: { + en: 'Tien', + ja: 'エッセル' + }, + filename: 'tien', + element: 'fire' + }, + { + name: { + en: 'Eahta', + ja: 'オクトー' + }, + filename: 'eahta', + element: 'earth' + }, + { + name: { + en: 'Niyon', + ja: 'ニオ' + }, + filename: 'niyon', + element: 'wind' + }, + { + name: { + en: 'Uno', + ja: 'ウーノ' + }, + filename: 'uno', + element: 'water' + }, + { + name: { + en: 'Fraux', + ja: 'フラウ' + }, + filename: 'fraux', + element: 'fire' + }, + { + name: { + en: 'Nier', + ja: 'ニーア' + }, + filename: 'nier', + element: 'dark' + }, + { + name: { + en: 'Estarriola', + ja: 'エスタリオラ' + }, + filename: 'estarriola', + element: 'wind' + }, + { + name: { + en: 'Alanaan', + ja: 'アラナン' + }, + filename: 'alanaan', + element: 'fire' + }, + { + name: { + en: 'Haaselia', + ja: 'ハーゼリーラ' + }, + filename: 'haaselia', + element: 'water' + }, + { + name: { + en: 'Maria Theresa', + ja: 'マリア・テレサ' + }, + filename: 'maria-theresa', + element: 'water' + }, + { + name: { + en: 'Geisenborger', + ja: 'ガイゼンボーガ' + }, + filename: 'geisenborger', + element: 'light' + }, + { + name: { + en: 'Lobelia', + ja: 'ロベリア' + }, + filename: 'lobelia', + element: 'earth' + }, + { + name: { + en: 'Caim', + ja: 'カイム' + }, + filename: 'caim', + element: 'earth' + }, + { + name: { + en: 'Katzelia', + ja: 'カッツェリーラ' + }, + filename: 'katzelia', + element: 'wind' + }, + { + name: { + en: 'Belial', + ja: 'ベリアル' + }, + filename: 'belial', + element: 'dark' + }, + { + name: { + en: 'Lucilius', + ja: 'ルシリウス' + }, + filename: 'lucilius', + element: 'dark' + }, + { + name: { + en: 'Lucifer', + ja: 'ルシファー' + }, + filename: 'lucifer', + element: 'light' + }, + { + name: { + en: 'Beelzebub', + ja: 'べルゼバブ' + }, + filename: 'beelzebub', + element: 'dark' + }, + { + name: { + en: 'Avatar', + ja: 'アバター' + }, + filename: 'avatar', + element: 'dark' + } +] \ No newline at end of file diff --git a/src/routes/api/settings/+server.ts b/src/routes/api/settings/+server.ts new file mode 100644 index 00000000..4c9f745c --- /dev/null +++ b/src/routes/api/settings/+server.ts @@ -0,0 +1,25 @@ +import { json } from '@sveltejs/kit' +import type { RequestHandler } from './$types' +import { setUserCookie } from '$lib/auth/cookies' +import type { UserCookie } from '$lib/types/UserCookie' + +export const POST: RequestHandler = async ({ cookies, request }) => { + try { + const userCookie = await request.json() as UserCookie + + // Calculate expiry date (60 days from now) + const expires = new Date() + expires.setDate(expires.getDate() + 60) + + // Set the user cookie with the updated data + setUserCookie(cookies, userCookie, { + secure: true, + expires + }) + + return json({ success: true }) + } catch (error) { + console.error('Failed to update settings cookie:', error) + return json({ error: 'Failed to update settings' }, { status: 500 }) + } +} \ No newline at end of file diff --git a/src/routes/settings/+page.server.ts b/src/routes/settings/+page.server.ts new file mode 100644 index 00000000..b332732e --- /dev/null +++ b/src/routes/settings/+page.server.ts @@ -0,0 +1,18 @@ +import { redirect } from '@sveltejs/kit' +import type { PageServerLoad } from './$types' +import { getAccountFromCookies, getUserFromCookies } from '$lib/auth/cookies' + +export const load: PageServerLoad = async ({ cookies, url }) => { + const account = getAccountFromCookies(cookies) + const currentUser = getUserFromCookies(cookies) + + // Redirect to login if not authenticated + if (!account || !currentUser) { + throw redirect(303, `/login?redirect=${encodeURIComponent(url.pathname)}`) + } + + return { + account, + currentUser + } +} \ No newline at end of file diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 00000000..70e8ff4a --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,366 @@ + + + + +
+
+

Account Settings

+

@{account.username}

+ +
+ {#if error} +
{error}
+ {/if} + + {#if success} +
Settings saved successfully!
+ {/if} + +
+ +
+ +
+
+ {currentPicture?.name[locale] +
+ +
+ + +
+ +
+ + + {#if account.role === 9} +
+ +
+ {/if} +
+ +
+ + +
+ +
+
+ + \ No newline at end of file