diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..9545791
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,24 @@
+# Example environment variables (copy to .env and fill values)
+SUPABASE_URL=
+SUPABASE_KEY=
+SECRET_KEY=change_this_secret
+BACKEND_URL=http://localhost:8000
+FRONTEND_URL=http://localhost:3000
+FRONTEND_URL_DEPLOYED=
+
+# SMTP (for email sending)
+SMTP_SERVER=smtp.example.com
+SMTP_PORT=465
+SMTP_USERNAME=
+SMTP_PASSWORD=
+SMTP_FROM_EMAIL=
+SMTP_FROM_NAME=CreaLab
+ADMIN_EMAIL=admin@crealab.com
+
+# Google Calendar (optional)
+GOOGLE_CALENDAR_SYNC_ENABLED=false
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+GOOGLE_REFRESH_TOKEN=
+GOOGLE_CALENDAR_ID=primary
+GOOGLE_CALENDAR_TIMEZONE=Europe/Paris
diff --git a/.gitignore b/.gitignore
index e689c6d..8f263ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,17 @@
+/.env
+/.env.local
+/.env.*.local
+node_modules/
+frontend/node_modules/
+backend/__pycache__/
+**/__pycache__/
+*.pyc
+frontend/build/
+dist/
+.venv/
+.idea/
+.vscode/
+*.log
# Node modules
node_modules/
npm-debug.log*
diff --git a/backend/models/event.py b/backend/models/event.py
index 436edb5..0c3f536 100644
--- a/backend/models/event.py
+++ b/backend/models/event.py
@@ -1,18 +1,20 @@
from pydantic import BaseModel
from datetime import datetime
+from typing import Optional
class EventCreate(BaseModel):
id: str
title: str
user: str
+ email: Optional[str] = None
start: datetime
startStr: str
end: datetime
endStr: str
duration: str
color: str
- id_card: str
+ id_card: Optional[str] = None
class Event(EventCreate):
diff --git a/backend/models/profile.py b/backend/models/profile.py
index bc494ac..88de560 100644
--- a/backend/models/profile.py
+++ b/backend/models/profile.py
@@ -1,12 +1,13 @@
from pydantic import BaseModel
+from typing import Optional
from .user import UserRole
class ProfileData(BaseModel):
- card_id: str
+ card_id: Optional[str] = None
first_name: str
last_name: str
email: str
- password: str
+ password: Optional[str] = None
role: UserRole
admin: bool = False
\ No newline at end of file
diff --git a/backend/routes/cards.py b/backend/routes/cards.py
index 8d4044f..8c8cd04 100644
--- a/backend/routes/cards.py
+++ b/backend/routes/cards.py
@@ -42,9 +42,15 @@ async def get_card(card_data: CardScan):
@router.get("/check-card/{card_id}")
def check_existing_card(card_id: str):
+ # Try to find by card_id first
result = supabase.table("CreaLab_visitors").select("*").eq("id_card", card_id).execute()
exists = len(result.data) > 0
if exists:
return {"exists": True, "data": result.data}
else:
+ # Fallback: also try to find by email (card_id could be an email)
+ result = supabase.table("CreaLab_visitors").select("*").eq("email", card_id).execute()
+ exists = len(result.data) > 0
+ if exists:
+ return {"exists": True, "data": result.data}
return {"exists": False}
\ No newline at end of file
diff --git a/backend/routes/events.py b/backend/routes/events.py
index f00191f..885cea9 100644
--- a/backend/routes/events.py
+++ b/backend/routes/events.py
@@ -80,14 +80,20 @@ def get_user_info_by_card_id(card_id: str) -> tuple:
def send_notification_email(event_data: dict, action: str) -> bool:
try:
- card_id = event_data.get("id_card")
- if not card_id:
- logging.warning("No card_id found for event, cannot send notification email")
- return False
-
- user_email, user_name = get_user_info_by_card_id(card_id)
+ # Try to get user email from event data first
+ user_email = event_data.get("email")
+ user_name = event_data.get("user", "Utilisateur")
+
+ # If email not in event data, try to get it from card_id (fallback)
if not user_email:
- logging.warning(f"No email found for card {card_id}, cannot send notification email")
+ card_id = event_data.get("id_card")
+ if card_id:
+ user_email, name = get_user_info_by_card_id(card_id)
+ if name:
+ user_name = name
+
+ if not user_email:
+ logging.warning("No email found for event, cannot send notification email")
return False
if action == "accept":
@@ -107,11 +113,18 @@ def sync_event_to_google_calendar(event_data: dict) -> None:
try:
event_payload = dict(event_data)
- card_id = event_data.get("id_card")
- if card_id:
- creator_email, _ = get_user_info_by_card_id(card_id)
- if creator_email:
- event_payload["creator_email"] = creator_email
+ # Try to get creator email from event data first
+ creator_email = event_data.get("email")
+
+ # If email not in event data, try to get it from card_id (fallback)
+ if not creator_email:
+ card_id = event_data.get("id_card")
+ if card_id:
+ creator_email, _ = get_user_info_by_card_id(card_id)
+
+ # Add creator email to event payload if available
+ if creator_email:
+ event_payload["creator_email"] = creator_email
result = sync_validated_event_to_admin_calendar(event_payload)
if result.get("synced"):
diff --git a/backend/routes/users.py b/backend/routes/users.py
index 11fa679..f168a5c 100644
--- a/backend/routes/users.py
+++ b/backend/routes/users.py
@@ -64,6 +64,7 @@ def login_user(request: Request, data: LoginData):
card_id = user.get("id_card")
scanned_card_id = data.scanned_card_id.strip() if data.scanned_card_id else None
+ # If a card is scanned, associate it with the account (optional step to make login faster)
if scanned_card_id:
validate_card_context(latest_card, scanned_card_id)
@@ -83,16 +84,10 @@ def login_user(request: Request, data: LoginData):
supabase.table("CreaLab_visitors").update({"id_card": scanned_card_id}).eq("email", data.email).execute()
card_id = scanned_card_id
+ latest_card["id"] = card_id
+ latest_card["ts"] = datetime.utcnow()
- if _is_temporary_card(card_id):
- raise HTTPException(
- status_code=status.HTTP_403_FORBIDDEN,
- detail="Scannez une carte pour finaliser la connexion"
- )
-
- latest_card["id"] = card_id
- latest_card["ts"] = datetime.utcnow()
-
+ # Allow login even without a valid card - card scanning is optional and used for faster identification
return {
"authenticated": True,
"card_id": card_id
@@ -114,7 +109,7 @@ def submit_data(request: Request, data: ProfileData):
if existing_email.data:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Un compte existe déjà avec cet email")
- normalized_card_id = data.card_id.strip()
+ normalized_card_id = data.card_id.strip() if data.card_id else NO_CARD_PLACEHOLDER
if normalized_card_id == NO_CARD_PLACEHOLDER:
normalized_card_id = f"{TEMP_CARD_PREFIX}{uuid4().hex[:12]}"
else:
@@ -125,7 +120,7 @@ def submit_data(request: Request, data: ProfileData):
if not is_school_email(data.email):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
- detail="Adresse email invalide: domaines autorises @devinci.fr ou @edu.vinci.fr"
+ detail="Adresse email invalide: domaines autorises @devinci.fr ou @edu.devinci.fr"
)
supabase.table("CreaLab_visitors").insert({
@@ -142,26 +137,31 @@ def submit_data(request: Request, data: ProfileData):
@router.post("/update-profile")
def update_profile(request: Request, data: ProfileData):
- logging.info("Updating profile for card: %s", data.card_id)
+ logging.info("Updating profile for email: %s", data.email)
validate_origin(request, FRONTEND_URLS)
- validate_card_context(latest_card, data.card_id)
-
+
+ # Use email as primary identifier for updating profile
+ # This works whether user logged in with card or without
supabase.table("CreaLab_visitors").update({
"first_name": data.first_name,
"last_name": data.last_name,
- "email": data.email,
"role": data.role.value
- }).eq("id_card", data.card_id).execute()
- return {"message": f"Profil pour la carte {data.card_id} mis à jour avec succès"}
+ }).eq("email", data.email).execute()
+
+ return {"message": f"Profil pour {data.email} mis à jour avec succès"}
@router.get("/get-profile/{card_id}")
def get_profile(request: Request, card_id: str):
validate_origin(request, FRONTEND_URLS)
- validate_card_context(latest_card, card_id)
-
+
+ # Try to get profile by card_id first (for backward compatibility)
result = supabase.table("CreaLab_visitors").select("*").eq("id_card", card_id).execute()
if len(result.data) > 0:
return {"found": True, "data": result.data[0]}
else:
+ # Fallback: also search by email (card_id could be an email in some cases)
+ result = supabase.table("CreaLab_visitors").select("*").eq("email", card_id).execute()
+ if len(result.data) > 0:
+ return {"found": True, "data": result.data[0]}
return {"found": False}
\ No newline at end of file
diff --git a/backend/utils/auth.py b/backend/utils/auth.py
index a2151bf..4ab0178 100644
--- a/backend/utils/auth.py
+++ b/backend/utils/auth.py
@@ -1,7 +1,7 @@
from datetime import datetime
-ALLOWED_SCHOOL_EMAIL_DOMAINS = ("@devinci.fr", "@edu.vinci.fr")
+ALLOWED_SCHOOL_EMAIL_DOMAINS = ("@devinci.fr", "@edu.devinci.fr")
def _normalize_origin(value: str) -> str:
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 42e2ac9..5c44d0f 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -21,7 +21,14 @@
display: flex;
justify-content: center;
align-items: center;
- filter: blur(3px);
+ filter: blur(2px);
+}
+
+.inscription-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
}
.login-container, .waiting-container {
@@ -32,7 +39,7 @@
background-color: #fff;
padding: 20px;
border-radius: 8px;
- gap: 16px;
+ gap : 16px;
}
.login-container h2, .waiting-container h2 {
@@ -43,7 +50,7 @@
.login-container p, .waiting-container p {
font-size: 1.25em;
font-weight: 500;
- margin-top: 12px;
+ margin-bottom: 12px;
color: #333;
}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index cbbd069..fb74f6c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -5,6 +5,7 @@ import Calendar from './pages/Calendar/Calendar';
import Inscription from './components/Inscription/Inscription';
import Connexion from './components/Connexion/Connexion';
import { setCardScanCallback } from './services/cardScanListener';
+import { getApiUrl } from './services/api';
import Bouton from './components/Bouton/Bouton';
type AppState = 'waiting' | 'login' | 'inscription' | 'calendar' | 'cardNotFound' | 'linkLogin';
@@ -28,7 +29,7 @@ function App() {
useEffect(() => {
const checkExistingCard = async (id: string) => {
try {
- const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+ const apiUrl = getApiUrl();
const response = await fetch(`${apiUrl}/check-card/${id}`);
const data = await response.json();
if (data.exists) {
@@ -118,8 +119,10 @@ function App() {
setAppState('calendar');
}}
/>
-
Vous n'êtes pas encore inscrit ?
- setAppState('inscription')} label="S'inscrire" />
+
+
Vous n'êtes pas encore inscrit ?
+
setAppState('inscription')} label="S'inscrire" />
+
>
);
diff --git a/frontend/src/components/Connexion/Connexion.css b/frontend/src/components/Connexion/Connexion.css
index ac01914..0542d46 100644
--- a/frontend/src/components/Connexion/Connexion.css
+++ b/frontend/src/components/Connexion/Connexion.css
@@ -18,7 +18,7 @@
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
- width: min(640px, 100%);
+ width: 100%;
}
.connexion_form .form_email {
@@ -56,7 +56,6 @@
@media (max-width: 768px) {
.connexion_container {
- width: calc(100vw - 48px);
padding: 24px;
}
diff --git a/frontend/src/components/Connexion/Connexion.tsx b/frontend/src/components/Connexion/Connexion.tsx
index a32ce55..50063dd 100644
--- a/frontend/src/components/Connexion/Connexion.tsx
+++ b/frontend/src/components/Connexion/Connexion.tsx
@@ -47,7 +47,7 @@ const Connexion = ({ onLoginSuccess, scannedCardId, onBack }: ConnexionProps) =>
const data = await response.json();
- if (!response.ok || !data?.authenticated || !data?.card_id) {
+ if (!response.ok || !data?.authenticated) {
throw new Error(data?.detail || "Identifiants invalides");
}
diff --git a/frontend/src/components/CreateCustomEvent/CreateCustomEvent.tsx b/frontend/src/components/CreateCustomEvent/CreateCustomEvent.tsx
index 41eba4a..554f9b5 100644
--- a/frontend/src/components/CreateCustomEvent/CreateCustomEvent.tsx
+++ b/frontend/src/components/CreateCustomEvent/CreateCustomEvent.tsx
@@ -47,7 +47,8 @@ const CreateCustomEvent = ({ openModal, setOpenModal, currentUser, onEventSave }
duration: String(end.getTime() - start.getTime()),
color,
user: currentUser.first_name + " " + currentUser.last_name,
- id_card: currentUser.id_card || "",
+ email: currentUser.email,
+ id_card: currentUser.id_card,
accepted: false,
};
diff --git a/frontend/src/components/Inscription/Inscription.css b/frontend/src/components/Inscription/Inscription.css
index 935d737..bb15d6f 100644
--- a/frontend/src/components/Inscription/Inscription.css
+++ b/frontend/src/components/Inscription/Inscription.css
@@ -17,6 +17,7 @@
.inscription_form {
display: grid;
grid-template-columns: 1fr 1fr;
+ width: 100%;
gap: 20px;
}
diff --git a/frontend/src/components/Inscription/Inscription.tsx b/frontend/src/components/Inscription/Inscription.tsx
index e30acb2..dc26711 100644
--- a/frontend/src/components/Inscription/Inscription.tsx
+++ b/frontend/src/components/Inscription/Inscription.tsx
@@ -33,7 +33,7 @@ const Inscription = ({card_id}: InscriptionInterface) => {
setFormError("");
if (!isSchoolEmail(email)) {
- setEmailError("L'email doit se terminer par @devinci.fr ou @edu.vinci.fr.");
+ setEmailError("L'email doit se terminer par @devinci.fr ou @edu.devinci.fr.");
return;
}
@@ -78,8 +78,8 @@ const Inscription = ({card_id}: InscriptionInterface) => {
return;
}
- if (error instanceof Error && error.message.includes("@devinci.fr ou @edu.vinci.fr")) {
- setEmailError("L'email doit se terminer par @devinci.fr ou @edu.vinci.fr.");
+ if (error instanceof Error && error.message.includes("@devinci.fr ou @edu.devinci.fr")) {
+ setEmailError("L'email doit se terminer par @devinci.fr ou @edu.devinci.fr.");
return;
}
diff --git a/frontend/src/hooks/useCalendarApi.ts b/frontend/src/hooks/useCalendarApi.ts
index 59a6527..5a8c447 100644
--- a/frontend/src/hooks/useCalendarApi.ts
+++ b/frontend/src/hooks/useCalendarApi.ts
@@ -47,6 +47,7 @@ export const useCalendarApi = () => {
borderColor: displayColor,
extendedProps: {
user: event.user,
+ email: event.email,
duration: event.duration,
id_card: event.id_card,
accepted: event.accepted
diff --git a/frontend/src/pages/Calendar/Calendar.tsx b/frontend/src/pages/Calendar/Calendar.tsx
index c2d38db..8394cc9 100644
--- a/frontend/src/pages/Calendar/Calendar.tsx
+++ b/frontend/src/pages/Calendar/Calendar.tsx
@@ -5,7 +5,7 @@ import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { EventContentArg } from "@fullcalendar/core";
import frLocale from '@fullcalendar/core/locales/fr';
-import { io, Socket } from "socket.io-client";
+import getSocket from "../../services/socketClient";
import "./CalendarComponent.css";
import "./FullCalendar.css";
import Sidebar from "../../layout/sidebar/Sidebar";
@@ -39,37 +39,25 @@ const Calendar = ({ card_id, setIsAdmin, setRefreshEvents }: CalendarEvent) => {
}, [userData, setIsAdmin]);
useEffect(() => {
- let socket: Socket | null = null;
-
- const initSocket = () => {
- const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8000';
-
- socket = io(apiUrl, {
- transports: ["websocket"]
- });
-
- socket.on("connect", () => {
- console.log("Calendar socket connected", socket?.id);
- });
-
- socket.on("events_updated", (data: { action: string; event?: CalendarEventData; event_id?: string }) => {
- console.log(`Event ${data.action}:`, data);
- fetchEvents();
- });
-
- socket.on("disconnect", () => {
- console.log("Calendar socket disconnected");
- });
+ const socket = getSocket();
+
+ const onEventsUpdated = (data: { action: string; event?: CalendarEventData; event_id?: string }) => {
+ console.log(`Event ${data.action}:`, data);
+ fetchEvents();
};
- if (card_id) {
- initSocket();
- }
+ socket.on("connect", () => {
+ console.log("Calendar socket connected", socket.id);
+ });
+
+ socket.on("events_updated", onEventsUpdated);
+
+ socket.on("disconnect", () => {
+ console.log("Calendar socket disconnected");
+ });
return () => {
- if (socket) {
- socket.disconnect();
- }
+ socket.off("events_updated", onEventsUpdated);
};
}, [card_id, fetchEvents]);
@@ -78,6 +66,7 @@ const Calendar = ({ card_id, setIsAdmin, setRefreshEvents }: CalendarEvent) => {
id: info.event.id,
title: info.event.title,
user: (info.event.extendedProps.user as string) || 'Unknown',
+ email: userData?.email,
start: info.event.start || new Date(),
startStr: info.event.startStr,
end: info.event.end || info.event.start || new Date(),
@@ -88,7 +77,7 @@ const Calendar = ({ card_id, setIsAdmin, setRefreshEvents }: CalendarEvent) => {
accepted: false
};
saveEvent(eventData);
- }, [card_id, saveEvent]);
+ }, [card_id, userData?.email, saveEvent]);
const renderEventContent = useCallback((eventInfo: EventContentArg) => {
const onDeleteClick = (event: React.MouseEvent) => {
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
new file mode 100644
index 0000000..c4466a7
--- /dev/null
+++ b/frontend/src/services/api.ts
@@ -0,0 +1,7 @@
+export const getApiUrl = (): string => {
+ return (process.env.REACT_APP_API_URL || process.env.REACT_APP_BACKEND_URL || 'http://localhost:8000');
+};
+
+export const getHeaders = (): Record => ({
+ 'Content-Type': 'application/json'
+});
diff --git a/frontend/src/services/cardScanListener.ts b/frontend/src/services/cardScanListener.ts
index f70ab53..07b7a94 100644
--- a/frontend/src/services/cardScanListener.ts
+++ b/frontend/src/services/cardScanListener.ts
@@ -1,20 +1,17 @@
-import { io, Socket } from "socket.io-client";
+import { getSocket } from './socketClient';
let previousId: string | null = null;
let onCardScanned: ((id: string) => void) | null = null;
-let socket: Socket | null = null;
export const setCardScanCallback = (callback: (id: string) => void) => {
onCardScanned = callback;
};
const initSocket = () => {
- if (socket) return;
- const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8000';
- socket = io(apiUrl, { transports: ["websocket"] });
+ const socket = getSocket();
socket.on("connect", () => {
- console.log("Card socket connected", socket?.id);
+ console.log("Card socket connected", socket.id);
});
socket.on("card_scanned", (cardId: string) => {
diff --git a/frontend/src/services/socketClient.ts b/frontend/src/services/socketClient.ts
new file mode 100644
index 0000000..2ce6a93
--- /dev/null
+++ b/frontend/src/services/socketClient.ts
@@ -0,0 +1,14 @@
+import { io, Socket } from "socket.io-client";
+import { getApiUrl } from './api';
+
+let socket: Socket | null = null;
+
+export const getSocket = (): Socket => {
+ if (!socket) {
+ const apiUrl = getApiUrl();
+ socket = io(apiUrl, { transports: ["websocket"] });
+ }
+ return socket;
+};
+
+export default getSocket;
diff --git a/frontend/src/types/globalTypes.ts b/frontend/src/types/globalTypes.ts
index 0b571b5..6a34ebd 100644
--- a/frontend/src/types/globalTypes.ts
+++ b/frontend/src/types/globalTypes.ts
@@ -3,7 +3,8 @@ export interface UserData {
last_name: string;
email: string;
role: string;
- [key: string]: string;
+ id_card?: string;
+ [key: string]: string | undefined;
}
export interface CalendarEvent {
card_id: string;
@@ -24,13 +25,14 @@ export interface CalendarEventData {
id: string;
title: string;
user: string;
+ email?: string;
start: Date;
startStr: string;
end: Date;
endStr: string;
duration: string;
color: string;
- id_card: string;
+ id_card?: string;
accepted: boolean;
}
@@ -43,8 +45,9 @@ export interface FormattedCalendarEvent {
borderColor: string;
extendedProps: {
user: string;
+ email?: string;
duration: string;
- id_card: string;
+ id_card?: string;
accepted: boolean;
};
}
@@ -53,13 +56,14 @@ export interface BackendEvent {
id: string;
title: string;
user: string;
+ email?: string;
start: string;
startStr: string;
end: string;
endStr: string;
duration: string;
color: string;
- id_card: string;
+ id_card?: string;
accepted: boolean;
}
diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts
index e3edc30..fab3f5f 100644
--- a/frontend/src/utils/auth.ts
+++ b/frontend/src/utils/auth.ts
@@ -1,4 +1,4 @@
-const ALLOWED_SCHOOL_EMAIL_DOMAINS = ["@devinci.fr", "@edu.vinci.fr"] as const;
+const ALLOWED_SCHOOL_EMAIL_DOMAINS = ["@devinci.fr", "@edu.devinci.fr"] as const;
export const isSchoolEmail = (emailValue: string): boolean => {
const normalizedEmail = emailValue.trim().toLowerCase();
diff --git a/mettre le s inscrire et.txt b/mettre le s inscrire et.txt
new file mode 100644
index 0000000..3f4e52c
--- /dev/null
+++ b/mettre le s inscrire et.txt
@@ -0,0 +1,34 @@
+mettre le s'inscrire et le connexion à coté
+cacher id card et role
+changer le log apres inscription
+corriger les fautes de frappes (dans le log apres inscription)
+ajouterelemnts pour clic et drag les evenements (ou clicquer sur calendrier directement pour creer un event)
+event custom --> ajouter descriptif à l'evenemtns et possibilité de selectionner plusiseurs tags (genre impression, peintures, etc..) / Laisser vite les créneax de base (sinon l'event se place a l'heure meme en validant)
+Separer les impressions perso / école dans les events pre fait (voir si atereil donner par l'utilisateur ou le crealab)
+Si @devin alors staff / si edu alors etudiant
+
+TODO projet (priorisé)
+
+[UI / Auth]
+- Aligner les boutons « Inscription » et « Connexion » sur la même ligne.
+- Masquer les champs sensibles (ID card, rôle) dans les formulaires/écrans publics.
+- Mettre à jour le message affiché après inscription.
+- Relire et corriger les libellés/messages pour supprimer les fautes.
+
+[Calendrier / Création d’événements]
+- Ajouter la création d’événement par clic direct sur le calendrier.
+- Ajouter le glisser-déposer d’événements.
+- Conserver les créneaux par défaut lors de la validation (éviter placement à l’heure courante).
+
+[Modèle d’événement]
+- Ajouter un champ « description » pour les événements personnalisés.
+- Permettre la sélection multiple de tags (ex. impression, peinture, etc.).
+
+[Catégorisation]
+- Séparer clairement les impressions « perso » vs « école » dans les événements prédéfinis.
+- Ajouter la source du matériel (utilisateur ou CreaLab) et l’afficher dans l’événement.
+
+[Qualité / Validation]
+- Vérifier les règles de visibilité des champs selon le rôle.
+- Tester les parcours complets: inscription, connexion, création événement, édition, drag & drop.
+- Ajouter une passe QA finale sur l’UX et les textes FR.
\ No newline at end of file
diff --git a/package.json b/package.json
index eeb23a1..948616f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "crealabvisitors",
- "version": "2.0.0",
+ "version": "2.1.0",
"description": "Une petite application de gestion des visiteurs pour l'espace CreaLab. Suit les visites, stocke les métadonnées de base et fournit une interface/API simple pour consulter les journaux des visiteurs.",
"homepage": "https://github.com/IIM-CDI/CreaLabVisitors#readme",
"bugs": {