diff --git a/app/components/repas/credentials.tsx b/app/components/repas/credentials.tsx
new file mode 100644
index 0000000..9d927a5
--- /dev/null
+++ b/app/components/repas/credentials.tsx
@@ -0,0 +1,148 @@
+import { Github, Loader2, Save } from "lucide-react";
+import { Button } from "../ui/button";
+import { Card } from "../ui/card";
+import { Input } from "../ui/input";
+import { Label } from "../ui/label";
+import { Link } from "react-router";
+import { getCredentialsStatus, testCredentials } from "~/lib/api";
+import { useState } from "react";
+import { toast } from "sonner";
+
+export default function BJRepas() {
+ const credentials = getSavedCredentials();
+ const [username, setUsername] = useState(credentials?.username || "");
+ const [password, setPassword] = useState(credentials?.password || "");
+
+ let intervalId: NodeJS.Timeout | null = null;
+ const [isLoading, setLoading] = useState(false);
+
+ const handleSaveCredentials = async () => {
+ try {
+ if (!username || !password) {
+ toast.error(
+ "Veuillez renseigner vos identifiants BJColle avant de continuer."
+ );
+ return;
+ }
+
+ await testCredentials(username, password);
+ setLoading(true);
+
+ // Try every second to check if the credentials are valid
+ intervalId = setInterval(async () => {
+ try {
+ const res = await getCredentialsStatus();
+
+ // Credentials are valid
+ if (res.authenticated === "SUCCESS") {
+ saveCredentials(username, password);
+ toast.success("Vos identifiants ont été enregistrés avec succès.");
+ setLoading(false);
+ if (intervalId) clearInterval(intervalId);
+ }
+ // Credentials are invalid
+ if (res.authenticated === "ERROR") {
+ toast.error(
+ "Identifiants invalides. Veuillez vérifier votre nom d'utilisateur et mot de passe."
+ );
+ setUsername("");
+ setPassword("");
+ setLoading(false);
+ if (intervalId) clearInterval(intervalId);
+ }
+ } catch (error) {
+ console.error("Error testing credentials:", error);
+ }
+ }, 1000);
+ } catch (error) {
+ console.error("Error testing credentials:", error);
+ toast.error(
+ "Une erreur est survenue lors de la validation des identifiants. Veuillez réessayer plus tard."
+ );
+ }
+ };
+ return (
+
+
Inscription aux repas
+
+
+ Pour vous inscrire aux repas (via BJ Repas), veuillez renseigner vos
+ identifiants BJColle ci-dessous.
+
+
+ Vos identifiants sont enregistrés localement sur votre appareil et ne
+ sont jamais stockés sur nos serveurs.
+
+
+
+
+
Vos identifiants BJColle
+
+
+ setUsername(e.target.value)}
+ placeholder="Entrez votre nom d'utilisateur"
+ className="mt-1"
+ />
+
+
+
+ setPassword(e.target.value)}
+ placeholder="Entrez votre mot de passe"
+ className="mt-1"
+ />
+
+
+
+
+
+
+ Pour en savoir plus sur la sécurité et la confidentialité, consultez
+ le code source du projet.{" "}
+
+
+
+
+
+
+ );
+}
+
+const saveCredentials = (username: string, password: string) => {
+ localStorage.setItem("bj_username", username);
+ localStorage.setItem("bj_password", password);
+};
+export const getSavedCredentials = () => {
+ const username = localStorage.getItem("bj_username");
+ const password = localStorage.getItem("bj_password");
+ if (username && password) {
+ return { username, password };
+ }
+ return null;
+};
diff --git a/app/components/repas/index.tsx b/app/components/repas/index.tsx
index a559c35..c87acb3 100644
--- a/app/components/repas/index.tsx
+++ b/app/components/repas/index.tsx
@@ -2,8 +2,8 @@ import { useState } from "react";
import { Tabs, TabsList, tabsStyle, TabsTrigger } from "~/components/ui/tabs";
import BottomNavigation from "~/components/bottom-nav";
import { CalendarCheck, ChefHat } from "lucide-react";
-import WIP from "./wip";
import Menus from "./menus";
+import BJRepas from "./credentials";
const tabs = [
{
@@ -16,7 +16,7 @@ const tabs = [
value: "bjrepas",
label: "BJ Repas",
icon: ,
- content: ,
+ content: ,
},
];
diff --git a/app/components/repas/menu-card.tsx b/app/components/repas/menu-card.tsx
index ab96bac..c1851ea 100644
--- a/app/components/repas/menu-card.tsx
+++ b/app/components/repas/menu-card.tsx
@@ -1,16 +1,23 @@
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
-import { Utensils, Salad, Cake, CookingPot, Icon, Carrot } from "lucide-react";
+import {
+ Utensils,
+ Salad,
+ Cake,
+ CookingPot,
+ Icon,
+ Carrot,
+ LogIn,
+ CalendarCheck,
+ Check,
+ CheckCheck,
+ Loader2,
+} from "lucide-react";
import { cheese } from "@lucide/lab";
-
-interface Course {
- name: string;
- description: string;
-}
-
-interface Meal {
- name: string;
- courses: Course[];
-}
+import { registerForMeal, type Meal } from "~/lib/api";
+import { Button } from "../ui/button";
+import { toast } from "sonner";
+import { useState } from "react";
+import { getSavedCredentials } from "./credentials";
const getCourseIcon = (courseName: string) => {
const name = courseName.toLowerCase();
@@ -23,7 +30,7 @@ const getCourseIcon = (courseName: string) => {
return ;
};
-export function DailyMenu({ meals }: { meals: Meal[] }) {
+export function DailyMenu({ meals, refetchMeals }: { meals: Meal[]; refetchMeals: () => Promise }) {
if (meals.length === 0) {
return (
@@ -34,6 +41,28 @@ export function DailyMenu({ meals }: { meals: Meal[] }) {
);
}
+ const [isLoading, setLoading] = useState(false);
+ const credentials = getSavedCredentials();
+
+ const handleRegister = async (id: number) => {
+ try {
+ if (!credentials) {
+ toast.error(
+ "Veuillez d'abord enregistrer vos identifiants dans l'onglet BJRepas."
+ );
+ return;
+ }
+ await registerForMeal(id, credentials.username, credentials.password);
+ await refetchMeals();
+ setLoading(false);
+ } catch (error) {
+ console.error("Error registering for meal:", error);
+ toast.error(
+ "Une erreur est survenue lors de l'inscription au repas. Veuillez réessayer."
+ );
+ }
+ };
+
return (
@@ -73,6 +102,33 @@ export function DailyMenu({ meals }: { meals: Meal[] }) {
))}
+
+ {meal.submittable &&
+ (meal.isRegistered ? (
+
+ ) : isLoading ? (
+
+ ) : (
+
+ ))}
))}
diff --git a/app/components/repas/menus.tsx b/app/components/repas/menus.tsx
index 03330c1..805d8f3 100644
--- a/app/components/repas/menus.tsx
+++ b/app/components/repas/menus.tsx
@@ -1,31 +1,34 @@
-import { useMenus } from "~/lib/api";
+import { useMeals, type Meal } from "~/lib/api";
import { DailyMenu } from "./menu-card";
import { DateTime } from "luxon";
import { useState } from "react";
import DayNavigation from "./day-navigation";
export default function Menus() {
- const { isLoading, error, menus } = useMenus();
+ const { isLoading, error, meals, refetch } = useMeals();
const [date, setDate] = useState(DateTime.now().startOf("day"));
const findMenuForDate = (date: DateTime) => {
- return menus?.find((menu: any) => {
- const menuDate = DateTime.fromISO(menu.date).startOf("day");
- return menuDate.equals(date);
+ return meals?.filter((meal) => {
+ const mealDate = DateTime.fromISO(meal.date).startOf("day");
+ return mealDate.equals(date);
});
};
return (
- {isLoading &&
Chargement des menus...
}
+ {isLoading &&
Chargement du menu...
}
{error && (
Erreur lors du chargement des menus.
)}
- {menus && menus.length === 0 &&
Aucun menu disponible.
}
+ {meals && meals.length === 0 &&
Aucun menu disponible.
}
{/* Menus */}
-
+
);
}
diff --git a/app/components/repas/wip.tsx b/app/components/repas/wip.tsx
deleted file mode 100644
index 57ee55f..0000000
--- a/app/components/repas/wip.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Github } from "lucide-react";
-import { Button } from "../ui/button";
-
-export default function WIP() {
- return (
-
-
- ⚠️ Cette fonctionnalité n’est pas encore implémentée.
-
-
- Vous pouvez contribuer au développement du projet ici :
-
-
-
- );
-}
diff --git a/app/lib/api.ts b/app/lib/api.ts
index 8b87684..f7a8767 100644
--- a/app/lib/api.ts
+++ b/app/lib/api.ts
@@ -461,22 +461,60 @@ export const useAverages = (period: string) => {
/**
* === REPAS API ===
*/
-const fetchMenus = async () => {
- return makeRequest(`/menus`, "Échec de la récupération des menus");
+const fetchMeals = async () => {
+ return makeRequest(`/meals`, "Échec de la récupération des menus");
};
-export const useMenus = () => {
+export const useMeals = () => {
const { data, ...props } = useQuery({
- queryKey: ["menus"],
- queryFn: fetchMenus,
+ queryKey: ["meals"],
+ queryFn: fetchMeals,
staleTime: Duration.fromObject({
- hours: 6, // 6 hours
+ hours: 0, // 6 hours
}).toMillis(),
gcTime: Duration.fromObject({
days: 1, // 1 day
}).toMillis(),
});
return {
- menus: data || [],
+ meals: (data as Meal[]) || [],
...props,
};
};
+
+export interface Meal {
+ id: number;
+ date: string;
+ name: string;
+ courses: { name: string; description: string }[];
+ submittable: boolean;
+ isRegistered?: boolean; // Optional field to indicate if the user is registered for this meal
+}
+
+export const registerForMeal = async (
+ mealId: number,
+ username: string,
+ password: string
+) => {
+ return makePostRequest(
+ `/meals/${mealId}`,
+ { username, password },
+ "Échec de l'inscription au repas",
+ "POST"
+ );
+};
+
+export const testCredentials = async (username: string, password: string) => {
+ return makePostRequest(
+ `/auth/test`,
+ { username, password },
+ "Échec de la vérification des identifiants",
+ "POST"
+ );
+};
+
+export const getCredentialsStatus = async () => {
+ return makeRequest(
+ `/auth/status`,
+ "Échec de la récupération du statut des identifiants"
+ );
+};