language-learning-app/frontend/src/routes/app/adventures/new/+page.server.ts
wilson 48bbcac9a6
Some checks failed
/ test (push) Has been cancelled
feat: [frontend] Build form and UI for creating a Choose Your Own
Adventure
2026-05-04 08:02:47 +01:00

202 lines
4.6 KiB
TypeScript

import { error, redirect, type Action, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { createAdventureApiAdventuresPost, getUserProfileBffUserProfileGet } from '@client';
import * as v from 'valibot';
import { randomItemInArray, shuffleArray } from '$lib';
import { formatLanguage } from '$lib/formatters';
const allVibes = [
'Melancholic',
'Gothic',
'Sun-drenched',
'Bleak',
'Whimsical',
'Eerie',
'Cosy',
'Tense',
'Witty',
'Propulsive',
'Mentor and student',
'Unlikely duo',
'Lone wolf',
'Queer-norm',
'Class tensions',
'Chosen family',
'Diaspora',
'Academia',
'Small town',
'The sea',
'Grand house',
'Road trip',
'A single night',
'Heist',
'Mystery box',
'Reluctant hero',
'Redemption',
'Animal companions',
'Gentle',
'Happy ever after',
'Bittersweet',
'Epistolary (letters / diary entries)',
"A big city that isn't the capital",
'Parenthood',
'Sly',
'Slapstick',
'Recovery',
'Political',
'Apocalyptic',
'Post-apocalyptic',
'Survival',
'War',
'Spy thriller',
'Time travel'
];
const allGenres = [
'Crime Fiction',
'Crime noir',
'Who-dun-it mystery',
'Paranormal',
'Horror',
'Psychological thriller',
'Romance',
'Family',
'Fantasy',
'Science Fiction'
];
export const load: PageServerLoad = async ({ locals }) => {
let languageCode = 'fr';
let competency = 'A1';
const profile = await getUserProfileBffUserProfileGet({
headers: {
Authorization: `Bearer ${locals.authToken}`
}
});
if (profile.error) {
console.error({ error: profile.error });
} else {
languageCode = profile.data?.learnable_languages[0].target_language ?? 'fr';
competency = profile.data?.learnable_languages[0].proficiencies[0] ?? competency;
}
return {
lengths: ['100-200 Words', '200-350 Words', '350-500 Words', '500-750 Words'],
competency: competency,
language: {
code: languageCode,
label: formatLanguage(languageCode)
},
competencies: ['A1', 'A2', 'B1', 'B2', 'C1'],
languages: [
{ code: 'fr', name: 'French' },
{ code: 'de', name: 'German' },
{ code: 'it', name: 'Italian' },
{ code: 'es', name: 'Spanish' },
{ code: 'pt', name: 'Portuguese' }
],
genres: allGenres.sort((a, b) => a.localeCompare(b)),
selectedGenre: randomItemInArray(allGenres),
eras: [
'Ancient/Classical',
'Medieval',
'1500-1800',
'Renaissance',
'19th century',
'Early 20th century',
'Mid-century',
'Contemporary',
'Near future',
'Far future'
],
settings: [
'Capital city',
'Large city (not the capital)',
'Urban',
'The suburbs',
'Rural',
'Pastoral',
'Small town',
'Wilderness',
'Space',
'Another planet'
],
allVibes: allVibes,
selectedVibes: shuffleArray(allVibes)
.slice(0, 6)
.sort((a, b) => a.localeCompare(b))
};
};
const CreateFormSchema = v.object({
vibes: v.array(v.string()),
genre: v.string(),
setting: v.string(),
competency: v.pipe(v.string(), v.picklist(['A1', 'A2', 'B1', 'B2', 'C1'])),
language: v.pipe(v.string(), v.picklist(['fr', 'it', 'de', 'it', 'es'])),
length: v.string(),
protagonist_gender: v.string(),
protagonist_age: v.string()
});
export const actions = {
default: async ({ locals, request }) => {
const { authToken } = locals;
const formData = await request.formData();
const data = v.safeParse(CreateFormSchema, {
vibes: formData.getAll('vibes') as string[],
genre: formData.get('genre') as string,
setting: formData.get('setting') as string,
competency: formData.get('competency') as string,
language: formData.get('language') as string,
length: formData.get('length') as string,
protagonist_gender: formData.get('protagonist_gender') as string,
protagonist_age: formData.get('protagonist_age') as string
});
if (data.success == false) {
console.error({ formError: data.issues });
throw error(400, {
message: data.issues.map((e) => `${e.path?.[0].key}: ${e.message}`).join(', ')
});
}
const {
competency,
language,
length,
genre,
setting,
protagonist_gender,
protagonist_age,
vibes
} = data.output;
const { data: apiData, error: apiError } = await createAdventureApiAdventuresPost({
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
competencies: [competency],
language: language,
genres: [genre],
setting: [setting],
vibes: vibes,
protagonist: [protagonist_gender, protagonist_age],
source_language: 'en',
entry_word_count_range: length,
max_entry_count: 6
}
});
if (apiError) {
return {
error: `Error creating new adventure, try again`
};
}
redirect(303, '/app/adventures?created=true');
}
} satisfies Actions;