diff --git a/apps/app/.storybook/main.ts b/apps/app/.storybook/main.ts index 4ec2156..7a8c806 100644 --- a/apps/app/.storybook/main.ts +++ b/apps/app/.storybook/main.ts @@ -6,15 +6,15 @@ const config: StorybookConfig = { '@storybook/addon-svelte-csf', '@storybook/addon-essentials', '@chromatic-com/storybook', - '@storybook/addon-interactions', + '@storybook/addon-interactions' ], framework: { name: '@storybook/sveltekit', options: { builder: { - viteConfigPath: '../vite.config.ts', - }, + viteConfigPath: '../vite.config.ts' + } } - }, + } }; export default config; diff --git a/apps/app/.vscode/extensions.json b/apps/app/.vscode/extensions.json index bb55775..8c41138 100644 --- a/apps/app/.vscode/extensions.json +++ b/apps/app/.vscode/extensions.json @@ -1,12 +1,12 @@ { - "recommendations": [ - "inlang.vs-code-extension", - "42Crunch.vscode-openapi", - "bruno-api-client.bruno", - "svelte.svelte-vscode", - "github.vscode-github-actions", - "GitHub.copilot", - "pixl-garden.BongoCat", - "golang.go" - ] -} \ No newline at end of file + "recommendations": [ + "inlang.vs-code-extension", + "42Crunch.vscode-openapi", + "bruno-api-client.bruno", + "svelte.svelte-vscode", + "github.vscode-github-actions", + "GitHub.copilot", + "pixl-garden.BongoCat", + "golang.go" + ] +} diff --git a/apps/app/messages/en.json b/apps/app/messages/en.json index ffd6074..9a09f50 100644 --- a/apps/app/messages/en.json +++ b/apps/app/messages/en.json @@ -24,6 +24,7 @@ "cancel": "Cancel", "city": "City", "child": "Child", + "change_profile_picture": "Change Profile Picture", "citizenship": "Citizenship", "close": "Close", "coffee": "Coffee", @@ -43,7 +44,9 @@ "deny": "Deny", "description": "Description", "details": "Details", + "died": "Died", "directions": "Directions", + "disclaimer": "Disclaimer", "document": "Document", "download": "Download", "edit": "Edit", diff --git a/apps/app/messages/hu.json b/apps/app/messages/hu.json index 8d39bfc..df04b1d 100644 --- a/apps/app/messages/hu.json +++ b/apps/app/messages/hu.json @@ -24,6 +24,7 @@ "cancel": "Mégse", "city": "Város", "child": "Gyermek", + "change_profile_picture": "Profilkép megváltoztatása", "citizenship": "Állampolgárság", "close": "Bezár", "coffee": "Kávé", @@ -43,7 +44,9 @@ "deny": "Elutasítás", "description": "Leírás", "details": "Részletek", + "died": "Elhunyt", "directions": "Útvonalak", + "disclaimer": "Felelősségkizárás", "document": "Dokumentum", "download": "Letöltés", "edit": "Szerkesztés", @@ -151,4 +154,4 @@ "welcome": "Üdvözöljük a Generációk Öröksége oldalán", "yes": "Igen", "zip_code": "Irányítószám" -} \ No newline at end of file +} diff --git a/apps/app/src/app.html b/apps/app/src/app.html index fa1cf5c..47b87c4 100644 --- a/apps/app/src/app.html +++ b/apps/app/src/app.html @@ -7,6 +7,8 @@ %sveltekit.head% -
%sveltekit.body%
+
+ %sveltekit.body% +
diff --git a/apps/app/src/hooks.server.ts b/apps/app/src/hooks.server.ts index da8b91b..92ef6de 100644 --- a/apps/app/src/hooks.server.ts +++ b/apps/app/src/hooks.server.ts @@ -28,7 +28,6 @@ const authHandle: Handle = async ({ event, resolve }) => { setSessionTokenCookie(event, token, session.expiresAt); } else { console.log('Session token is invalid'); - console.log(session, token); deleteSessionTokenCookie(event); } diff --git a/apps/app/src/lib/ThemeSelect.svelte b/apps/app/src/lib/ThemeSelect.svelte index 3a579e5..e7a02c3 100644 --- a/apps/app/src/lib/ThemeSelect.svelte +++ b/apps/app/src/lib/ThemeSelect.svelte @@ -41,7 +41,7 @@ - {:else} - {JSON.stringify(value) ?? '-'} - {/if} -

- - {/if} - {/each} + {#each Object.entries(draftPerson) as [key, value]} + {#if !skipFields.includes(key) && value !== undefined && value !== null} +
+

+ {callMessageFunction(key as MessageKeys) || key}: + {#if editorMode} + + {:else} + {JSON.stringify(value) ?? '-'} + {/if} +

+
+ {/if} + {/each} diff --git a/apps/app/src/lib/profile/ProfileHeader.svelte b/apps/app/src/lib/profile/ProfileHeader.svelte index 23f9429..2d5326f 100644 --- a/apps/app/src/lib/profile/ProfileHeader.svelte +++ b/apps/app/src/lib/profile/ProfileHeader.svelte @@ -1,30 +1,103 @@
- {#if draftPerson.profile_picture} -
- {profile_picture()} -
- {/if} - +
+ {profile_picture()} + {#if editorMode} + + {/if} +

{first_name()}: {#if editorMode}{:else}{draftPerson.first_name ?? '-'}{/if}

{last_name()}: {#if editorMode}{:else}{draftPerson.last_name ?? '-'}{/if}

{middle_name()}: {#if editorMode}{:else}{draftPerson.middle_name ?? '-'}{/if}

-

{born()}: {#if editorMode}{:else}{draftPerson.born ?? '-'}{/if}

-

{biological_sex()}: {#if editorMode}{:else}{draftPerson.biological_sex ?? '-'}{/if}

+

{born()}: + +

+

{died()}: + +

+

{biological_sex()}: + {#if editorMode} + + {:else}{callMessageFunction(draftPerson.biological_sex as MessageKeys) ?? '-'}{/if}

{email()}: {#if editorMode}{:else}{draftPerson.email ?? '-'}{/if}

-

Limit: {#if editorMode}{:else}{draftPerson.limit ?? '-'}{/if}

{mothers_first_name()}: {#if editorMode}{:else}{draftPerson.mothers_first_name ?? '-'}{/if}

{mothers_last_name()}: {#if editorMode}{:else}{draftPerson.mothers_last_name ?? '-'}{/if}

+

{id()}:{draftPerson.id ?? '-'}

+

Limit:{draftPerson.limit ?? '-'}

diff --git a/apps/app/src/lib/profile/create/Modal.svelte b/apps/app/src/lib/profile/create/Modal.svelte new file mode 100644 index 0000000..b309cdb --- /dev/null +++ b/apps/app/src/lib/profile/create/Modal.svelte @@ -0,0 +1,175 @@ + + + diff --git a/apps/app/src/lib/profile/create/validate_fields.ts b/apps/app/src/lib/profile/create/validate_fields.ts new file mode 100644 index 0000000..60eb5ec --- /dev/null +++ b/apps/app/src/lib/profile/create/validate_fields.ts @@ -0,0 +1,56 @@ +import type { components } from '$lib/api/api.gen.js'; + +export function validatePersonRegistration(data: components['schemas']['PersonRegistration']): string | null { + if (!data.first_name || data.first_name.trim() === "") { + return "First name is required."; + } + + if (!data.last_name || data.last_name.trim() === "") { + return "Last name is required."; + } + + if (data.email !== undefined && data.email !== null && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { + return "Invalid email format."; + } + + if (!data.born || isNaN(Date.parse(data.born))) { + return "Valid birth date is required."; + } + + if (!data.biological_sex || !['male', 'female', 'intersex', 'unknown', 'other'].includes(data.biological_sex.toString())){ + return 'Invalid value for biological sex. Must be male female, intersex, unknown, or other.'; + } + + if (!data.mothers_first_name || data.mothers_first_name.trim() === "") { + return "Mother's first name is required."; + } + + if (!data.mothers_last_name || data.mothers_last_name.trim() === "") { + return "Mother's last name is required."; + } + + return null; // No errors +} + +export function validateFamilyRelationship(relationship: components['schemas']['FamilyRelationship'] & {type:string}): string | null { + const validRelationships = [ + "child", + "parent", + "spouse", + "sibling" + ]; + + if (!validRelationships.includes(relationship.type)) { + return `Invalid family relationship. Must be one of ${validRelationships.join(', ')}.`; + } + + if (relationship.from !== undefined && relationship.from !== null && isNaN(Date.parse(relationship.from))) { + return "Valid date is required for 'from' field."; + } + + if (relationship.to !== undefined && relationship.to !== null && isNaN(Date.parse(relationship.to))) { + return "Valid date is required for 'to' field."; + } + + return null; // No errors +} \ No newline at end of file diff --git a/apps/app/src/lib/server/session.ts b/apps/app/src/lib/server/session.ts index 1650fad..77ffbd8 100644 --- a/apps/app/src/lib/server/session.ts +++ b/apps/app/src/lib/server/session.ts @@ -34,7 +34,11 @@ export async function invalidateUserSessions(userId: number, sessions: KVNamespa } } -export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: EpochTimeStamp): void { +export function setSessionTokenCookie( + event: RequestEvent, + token: string, + expiresAt: EpochTimeStamp +): void { event.cookies.set('session', token, { httpOnly: true, path: '/', diff --git a/apps/app/src/routes/+layout.svelte b/apps/app/src/routes/+layout.svelte index 041cfc5..43c12e2 100644 --- a/apps/app/src/routes/+layout.svelte +++ b/apps/app/src/routes/+layout.svelte @@ -10,8 +10,8 @@ {@render children()} -
+
- +
diff --git a/apps/app/src/routes/+page.svelte b/apps/app/src/routes/+page.svelte index 162868a..38e0f9d 100644 --- a/apps/app/src/routes/+page.svelte +++ b/apps/app/src/routes/+page.svelte @@ -1,23 +1,28 @@ @@ -58,6 +108,9 @@ - + {#if openPersonPanel} + { + openPersonPanel = false; + }} + /> + {/if} + {#if createPerson} + + {/if} {#if openPersonMenu !== undefined} {/if} diff --git a/apps/app/src/routes/api/family_tree/+page.server.ts b/apps/app/src/routes/api/family_tree/+page.server.ts new file mode 100644 index 0000000..2384f10 --- /dev/null +++ b/apps/app/src/routes/api/family_tree/+page.server.ts @@ -0,0 +1,44 @@ +import { error, redirect } from '@sveltejs/kit'; +import { client } from '$lib/api/client'; +import type { RequestEvent } from './$types'; +import type { components } from '$lib/api/api.gen'; + + +async function GET(event: RequestEvent): Promise { + if (event.locals.session === null /*|| event.locals.familytree === nul*/) { + return redirect(302, '/login'); + } + + const response = await client.GET(event.url.searchParams.get('with_out_spouse') === 'true' ? '/family-tree-with-spouses':'/family-tree', { + params: { + header: { 'X-User-ID': event.locals.session.userId } + } + }); + + if (response.response.status !== 200) { + return error(500, { + message: response.error?.msg || 'Failed to fetch family tree' + }); + } + + if (response.data === null || response.data?.people === null || response.data?.people === undefined || response.data?.people.length === 0) { + return error(500, { + message: 'Family tree is empty' + }); + } + + var peopleToReturn : Array = []; + for (const person of response.data.people) { + let newPerson = person + + if (newPerson.profile_picture!== null && newPerson.profile_picture !== undefined) { + + } + + peopleToReturn.push(newPerson); + } + + return new Response(JSON.stringify(peopleToReturn), { + status: 200, + }); +} diff --git a/apps/app/src/routes/api/person/+server.ts b/apps/app/src/routes/api/person/+server.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/app/src/routes/login/google/callback/+page.server.ts b/apps/app/src/routes/login/google/callback/+page.server.ts index 2f70597..2a8bde8 100644 --- a/apps/app/src/routes/login/google/callback/+page.server.ts +++ b/apps/app/src/routes/login/google/callback/+page.server.ts @@ -71,7 +71,11 @@ export const load: PageServerLoad = async (event: RequestEvent) => { } const sessionToken = generateSessionToken(String(response.data.Id)); - const session = await createSession(sessionToken, response.data.Id, event.platform.env.GH_SESSIONS) + const session = await createSession( + sessionToken, + response.data.Id, + event.platform.env.GH_SESSIONS + ); if (session === null) { return error(500, { message: 'Failed to create session' @@ -84,8 +88,6 @@ export const load: PageServerLoad = async (event: RequestEvent) => { } } - - let personP: PersonProperties = { google_id: sub, first_name: first_name, @@ -163,7 +165,9 @@ async function register(event: RequestEvent) { field: biological_sex() }) }); - } else if (!['male', 'female', 'intersex', 'unknown', 'other'].includes(bbiological_sex.toString())) { + } else if ( + !['male', 'female', 'intersex', 'unknown', 'other'].includes(bbiological_sex.toString()) + ) { return fail(400, { message: `Invalid value for biological_sex. Must be one of "male", "female", "intersex", "unknown", or "other".` }); @@ -194,17 +198,17 @@ async function register(event: RequestEvent) { born: parsed_date.toISOString().split('T')[0], mothers_first_name: mothers_first_name_f as string, mothers_last_name: mothers_last_name_f as string, - biological_sex: bbiological_sex as components['schemas']['PersonRegistration']['biological_sex'], + biological_sex: + bbiological_sex as components['schemas']['PersonRegistration']['biological_sex'], limit: StorageLimit }; - let response = await client - .POST('/person/google/{google_id}', { - params: { - path: { google_id: google_id.toString() } - }, - body: personP - }) + let response = await client.POST('/person/google/{google_id}', { + params: { + path: { google_id: google_id.toString() } + }, + body: personP + }); if (response.response.status !== 200) { return fail(400, { @@ -212,9 +216,13 @@ async function register(event: RequestEvent) { }); } + if (response.data === undefined) { + return fail(400, { + message: failed_to_create_user() + 'No user data returned' + }); + } - if (!response.data?.Id) { - console.log(response.data) + if (response.data.Id === undefined) { return fail(400, { message: failed_to_create_user() + 'No user ID returned' }); @@ -231,7 +239,7 @@ async function register(event: RequestEvent) { sessionToken, response.data.Id, event.platform.env.GH_SESSIONS - ) + ); if (session === null) { return fail(500, { message: failed_to_create_user() + 'Failed to create session' diff --git a/apps/app/src/routes/login/google/callback/+page.svelte b/apps/app/src/routes/login/google/callback/+page.svelte index e059ec6..2748f91 100644 --- a/apps/app/src/routes/login/google/callback/+page.svelte +++ b/apps/app/src/routes/login/google/callback/+page.svelte @@ -15,7 +15,8 @@ biological_sex, male, female, - other + other, + intersex } from '$lib/paraglide/messages'; import { onMount } from 'svelte'; import { enhance } from '$app/forms'; @@ -129,7 +130,7 @@ type="text" class="input pika-single" id="birth_date" - value={born()} + placeholder={born()} bind:this={birth_date} /> @@ -142,6 +143,7 @@ > + diff --git a/apps/app/src/routes/login/page.stories.ts b/apps/app/src/routes/login/page.stories.ts new file mode 100644 index 0000000..2f20160 --- /dev/null +++ b/apps/app/src/routes/login/page.stories.ts @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from '@storybook/svelte'; +import SignInPage from './+page.svelte'; + +const meta = { + title: 'login/+page', + component: SignInPage, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => { + return { + Component: SignInPage + }; + } +}; diff --git a/apps/app/src/routes/logout/+server.ts b/apps/app/src/routes/logout/+server.ts index 0048536..e7bf2e5 100644 --- a/apps/app/src/routes/logout/+server.ts +++ b/apps/app/src/routes/logout/+server.ts @@ -17,4 +17,4 @@ export async function GET(event: RequestEvent): Promise { deleteSessionTokenCookie(event); return redirect(302, '/login'); -} \ No newline at end of file +} diff --git a/apps/app/wrangler.jsonc b/apps/app/wrangler.jsonc index fabbc01..3591de7 100644 --- a/apps/app/wrangler.jsonc +++ b/apps/app/wrangler.jsonc @@ -5,9 +5,7 @@ { "$schema": "node_modules/wrangler/config-schema.json", "name": "generations-heritage", - "compatibility_flags": [ - "nodejs_compat" - ], + "compatibility_flags": ["nodejs_compat"], "compatibility_date": "2025-02-14", "pages_build_output_dir": ".svelte-kit/cloudflare", "observability": { @@ -99,4 +97,4 @@ * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings */ // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] -} \ No newline at end of file +}