diff --git a/backend/app/models.py b/backend/app/models.py index c5d921e0fc9836c06c148f8e538cc25dd6a9568b..d8fa248b72cc1119990a7690199b19ea25ceb9e5 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -33,6 +33,7 @@ class Session(Base): is_active = Column(Boolean, default=True) start_time = Column(DateTime, default=datetime.datetime.now) end_time = Column(DateTime, default=lambda: datetime.datetime.now() + datetime.timedelta(hours=12)) + language = Column(String, default="fr") users = relationship("User", secondary="user_sessions", back_populates="sessions") diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 3268bde53a1e6461cc3cd43220e742c670d5e307..d8146c45bb17d392c0cb384336e8f943e7c2ce3e 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -41,13 +41,14 @@ class Session(BaseModel): users: list[User] start_time: datetime.datetime end_time: datetime.datetime + language: str class Config: from_attributes = True class SessionUpdate(BaseModel): - token: str | None = None is_active: bool | None = None + language: str | None = None class Config: from_attributes = True diff --git a/frontend/src/app.css b/frontend/src/app.css index 6d418f23ef3d6db5822d707936df4b0aba117c83..ea75944608958336c4451d62d3653568055ccc9b 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -3,7 +3,7 @@ @tailwind utilities; .button { - @apply bg-secondary text-white font-bold py-2 px-4 rounded hover:bg-secondaryHover hover:cursor-pointer; + @apply bg-secondary text-white font-bold py-2 px-4 rounded hover:bg-secondaryHover hover:cursor-pointer disabled:border-secondary disabled:bg-transparent disabled:border-2 disabled:text-secondary disabled:hover:cursor-default; } .test1 { diff --git a/frontend/src/lib/api/apiInstance.ts b/frontend/src/lib/api/apiInstance.ts index 3a66c00e816fc4178861d36bcfddd86a9ab2678b..325ef0b08acc0cd16ef43ba7be43cc80019fcae0 100644 --- a/frontend/src/lib/api/apiInstance.ts +++ b/frontend/src/lib/api/apiInstance.ts @@ -8,13 +8,11 @@ import { import session from '$lib/stores/JWTSession'; import { jwtDecode } from 'jwt-decode'; import type { JWTContent } from '$lib/utils/login'; - -export const API_URL = 'http://localhost:8000/api/v1'; -export const WS_URL = 'ws://localhost:8000/api/v1/ws'; +import config from '$lib/config'; export const axiosPublicInstance = axios.create({ ...axios.defaults, - baseURL: API_URL, + baseURL: config.API_URL, validateStatus: () => true, headers: { 'Content-Type': 'application/json' @@ -23,7 +21,7 @@ export const axiosPublicInstance = axios.create({ export const axiosInstance = axios.create({ ...axios.defaults, - baseURL: API_URL, + baseURL: config.API_URL, validateStatus: () => true, headers: { 'Content-Type': 'application/json' diff --git a/frontend/src/lib/api/sessions.ts b/frontend/src/lib/api/sessions.ts index caa6d7a919c9efb146b765282f0b0720d798c36b..ac3f5a0a24991de39c7c9900954320e36dade8a1 100644 --- a/frontend/src/lib/api/sessions.ts +++ b/frontend/src/lib/api/sessions.ts @@ -50,3 +50,14 @@ export async function createMessageAPI(id: number, content: string): Promise<num return response.data; } + +export async function patchLanguageAPI(id: number, language: string) { + const response = await axiosInstance.patch(`/sessions/${id}`, { language }); + + if (response.status !== 204) { + toastAlert('Failed to change language'); + return false; + } + + return true; +} diff --git a/frontend/src/lib/components/sessions/editParticipants.svelte b/frontend/src/lib/components/sessions/editParticipants.svelte index d209f6af8326cda93db8624fea98f9a4135f207c..38f32cff4b0a997d715f2aec725e6d2bd55976fb 100644 --- a/frontend/src/lib/components/sessions/editParticipants.svelte +++ b/frontend/src/lib/components/sessions/editParticipants.svelte @@ -5,13 +5,17 @@ import Select from 'svelte-select'; import { get } from 'svelte/store'; import { onMount } from 'svelte'; - import { Icon, XMark } from 'svelte-hero-icons'; + import { Icon, Language, XMark } from 'svelte-hero-icons'; import { _ } from '$lib/services/i18n'; + import config from '$lib/config'; export let session: Session; export let onClose: () => void; + let selectedLanguage: string; + $: currentLanguage = session.language; + let sessionUsers = session.users; let selected: { value: User; label: string } | null; @@ -47,6 +51,12 @@ selected = null; calculateDropDownUsers(); } + + async function changeLanguage() { + if (session.language === selectedLanguage) return; + await session.changeLanguage(selectedLanguage); + currentLanguage = session.language; + } </script> <!-- svelte-ignore a11y-no-static-element-interactions --> @@ -55,22 +65,39 @@ on:click|preventDefault|stopPropagation={onClose} class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center" > - <div class="bg-white w-full min-w-fit max-w-md rounded p-4" on:click|stopPropagation> + <div class="bg-white w-full min-w-fit max-w-lg rounded p-4" on:click|stopPropagation> <div class="float-right w-8 hover:text-red-500 hover:cursor-pointer" on:click={onClose}> <Icon src={XMark} /> </div> - <h1 class="text-xl font-bold pb-4">{$_('home.participants')}</h1> + <h1 class="text-xl font-bold pb-4">{$_('home.learningLanguage')}</h1> + <div class="flex"> + <select bind:value={selectedLanguage} class="min-w-fit w-full h-12 p-2"> + {#each config.LEARNING_LANGUAGES as language} + <option selected={session.language == language} value={language} + >{$_(`utils.language.${language}`)}</option + > + {/each} + </select> + <button + class="button w-1/4 ml-4" + disabled={currentLanguage === selectedLanguage} + on:click={changeLanguage}>{$_('home.confirm')}</button + > + </div> + <h1 class="text-xl font-bold py-4">{$_('home.participants')}</h1> <table class="w-full shadow-md"> <thead class="bg-gray-200 uppercase text-sm"> <tr> + <th class="py-2 px-6">{$_('home.nickname')}</th> <th class="py-2 px-6">{$_('home.email')}</th> <th class="py-2 px-6">{$_('home.actions')}</th> </tr> </thead> {#each sessionUsers as user (user.id)} <tr class="even:bg-white odd:bg-gray-100 text-center"> - <td class="py-3 px-6 w-2/3">{user.email}</td> - <td class="py-3 px-6 w-1/3"> + <td class="py-3 px-6 w-1/4">{user.nickname}</td> + <td class="py-3 px-6 w-2/4">{user.email}</td> + <td class="py-3 px-6 w-1/4"> <button on:click={() => removeParticipant(user)}> <Icon src={XMark} class="w-6" /> </button> @@ -78,14 +105,18 @@ </tr> {/each} <tr class="text-center"> - <td + <td colspan="2" ><Select items={dropDownUsers} bind:value={selected} placeholder={$_('home.participantPlaceholder')} ></Select></td > - <td><button on:click={addParticipant} class="button">{$_('home.add')}</button></td> + <td + ><button on:click={addParticipant} class="button" disabled={selected === null} + >{$_('home.add')}</button + ></td + > </tr> </table> </div> diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c296534b61ccf34b5d24b038d12031a179386b9 --- /dev/null +++ b/frontend/src/lib/config.ts @@ -0,0 +1,5 @@ +export default { + API_URL: 'http://localhost:8000/api/v1', + WS_URL: 'ws://localhost:8000/api/v1/ws', + LEARNING_LANGUAGES: ['fr', 'en'] +}; diff --git a/frontend/src/lib/types/session.ts b/frontend/src/lib/types/session.ts index 0f4636d431fb878c39e30e08759de4a8716cf2d8..7deca4fa020d9cd441172d468e83d30464ee279a 100644 --- a/frontend/src/lib/types/session.ts +++ b/frontend/src/lib/types/session.ts @@ -1,9 +1,10 @@ import { toastAlert } from '$lib/utils/toasts'; import { get, writable, type Writable } from 'svelte/store'; import User from './user'; -import { WS_URL, axiosInstance } from '$lib/api/apiInstance'; -import { createMessageAPI, getMessagesAPI } from '$lib/api/sessions'; +import { axiosInstance } from '$lib/api/apiInstance'; +import { createMessageAPI, getMessagesAPI, patchLanguageAPI } from '$lib/api/sessions'; import Message from './message'; +import config from '$lib/config'; const { subscribe, set, update } = writable<Session[]>([]); @@ -24,6 +25,7 @@ export default class Session { private _created_at: Date; private _start_time: Date; private _end_time: Date; + private _language: string; private _ws: WebSocket | null = null; private _ws_connected = writable(false); @@ -34,7 +36,8 @@ export default class Session { users: User[], created_at: Date, start_time: Date, - end_time: Date + end_time: Date, + language: string ) { this._id = id; this._token = token; @@ -44,6 +47,7 @@ export default class Session { this._created_at = created_at; this._start_time = start_time; this._end_time = end_time; + this._language = language; } get id(): number { @@ -74,6 +78,10 @@ export default class Session { return this._end_time; } + get language(): string { + return this._language; + } + get messages(): Writable<Message[]> { return this._messages; } @@ -159,12 +167,19 @@ export default class Session { return message; } + async changeLanguage(language: string): Promise<boolean> { + const res = await patchLanguageAPI(this.id, language); + if (!res) return false; + this._language = language; + return true; + } + public wsConnect() { if (get(this._ws_connected)) return; const token = localStorage.getItem('accessToken'); - this._ws = new WebSocket(`${WS_URL}/${token}/${this.id}`); + this._ws = new WebSocket(`${config.WS_URL}/${token}/${this.id}`); this._ws.onopen = () => { this._ws_connected.set(true); @@ -237,7 +252,8 @@ export default class Session { [], new Date(json.created_at), new Date(json.start_time), - new Date(json.end_time) + new Date(json.end_time), + json.language ); session._users = User.parseAll(json.users); diff --git a/frontend/static/lang/fr.json b/frontend/static/lang/fr.json index 9b4b04474075842ef6bcf298f29804c475bf6de2..7d2fc132fe99fbbc83f3e7502f1ed7f5901db28d 100644 --- a/frontend/static/lang/fr.json +++ b/frontend/static/lang/fr.json @@ -16,7 +16,15 @@ "deleteSessionConfirm": "Êtes-vous sûr de vouloir supprimer cette session ? Cette action est irréversible.", "createSession": "Créer une nouvelle session", "participantPlaceholder": "Selectionnez", - "remainingDuration": "Durée restante" + "remainingDuration": "Durée restante", + "nickname": "Pseudo", + "learningLanguage": "Langue d'apprentissage", + "confirm": "Confirmer" + }, + "login": { + "email": "Email", + "password": "Mot de passe", + "login": "Se connecter" }, "utils": { "month": { @@ -32,11 +40,10 @@ "october": "octobre", "november": "novembre", "december": "décembre" + }, + "language": { + "fr": "Français", + "en": "Anglais" } - }, - "login": { - "email": "Email", - "password": "Mot de passe", - "login": "Se connecter" } }