NextGraph integration (WIP), broker banner, and feature-based architecture
- Add NextGraph data layer with @ng-org/orm, SHEX shapes (Event, UserProfile, Participation), session management, and FestipodDataContext with dual-mode operation (connected via NextGraph or local seed data) - Add BrokerBanner and NgStatus components showing connection status - Refactor to feature-based architecture: organize code by business domain (event, user, home, auth, workshop, meeting, notification) instead of technical layer. Modules only import from shared/, never from each other - Collocate BDD features and step definitions with their modules: event-specific steps in event/steps/, user steps in user/steps/, shared generic steps remain in shared/steps/ - Set up multi-layer BDD structure (frontend/backend/e2e steps per module) - Add project documentation (AGENTS.md, .project/knowledge/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# language: fr
|
||||
@USER @priority-1
|
||||
Fonctionnalité: US-10 Visualiser la fiche/le profil d'un participant
|
||||
En tant qu'utilisateur
|
||||
Je peux sélectionner un individu dans la liste des inscrits à un événement/atelier
|
||||
Afin de voir les événements auxquels la personne a participé et voir un formulaire de contact
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder au profil d'un participant
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Quand je clique sur un participant
|
||||
Alors je vois l'écran "user-profile"
|
||||
|
||||
Scénario: Voir les événements du participant
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Alors je peux voir les événements auxquels l'utilisateur a participé
|
||||
|
||||
Scénario: Voir la localisation des événements
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Alors les événements affichent leur localisation et distance
|
||||
|
||||
Scénario: Voir le formulaire de contact
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Alors je peux contacter l'utilisateur
|
||||
|
||||
Scénario: Vérifier les informations du profil
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Voir les détails du profil utilisateur
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Alors l'écran affiche les informations du profil
|
||||
@@ -0,0 +1,34 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-12 Consulter la carte/tableau des événements
|
||||
En tant qu'utilisateur
|
||||
Je peux consulter la carte/tableau des événements auxquels j'ai participé
|
||||
En filtrant les événements par dates ou par personne
|
||||
Afin d'avoir une vue consolidée des événements et lieux de rencontre
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder à la liste des événements depuis le profil
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Accéder à la liste des événements depuis découvrir
|
||||
Étant donné que je suis sur la page "découvrir"
|
||||
Alors je peux voir la liste des événements
|
||||
|
||||
Scénario: Filtrer par date
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Filtrer par personne
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Alors je peux voir les événements auxquels l'utilisateur a participé
|
||||
|
||||
Scénario: Vérifier les données de l'écran événements
|
||||
Étant donné que je suis sur la page "découvrir"
|
||||
Alors les événements affichent leur lieu
|
||||
|
||||
Scénario: Vérifier les données de l'écran profil
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Voir la vue carte des événements
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,29 @@
|
||||
# language: fr
|
||||
@USER @priority-1
|
||||
Fonctionnalité: US-15 Visualiser les inscrits à un atelier/événement
|
||||
En tant qu'utilisateur
|
||||
Je peux visualiser les inscrits à un atelier/événement
|
||||
En sélectionnant l'atelier/l'événement désiré dans la liste
|
||||
Afin de consulter la liste des inscrits triée par ordre alphabétique
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder à la liste des inscrits d'un événement
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Alors je peux voir la liste des participants
|
||||
|
||||
Scénario: Accéder à la liste des inscrits d'un atelier
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Voir la liste des participants d'un événement
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Alors l'écran contient une section "Participants"
|
||||
|
||||
Scénario: Voir la liste des participants d'un atelier
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Cliquer sur un inscrit pour voir son profil
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Quand je clique sur un participant
|
||||
Alors je vois l'écran "user-profile"
|
||||
@@ -0,0 +1,38 @@
|
||||
# language: fr
|
||||
@USER @priority-1
|
||||
Fonctionnalité: US-20 Voir le profil des personnes faisant partie de mon réseau
|
||||
En tant qu'utilisateur
|
||||
Je peux voir le profil des personnes faisant partie de mon réseau
|
||||
Ainsi que le profil des personnes publiques
|
||||
Et consulter la description de l'événement afin de savoir si je veux participer
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder à mon profil
|
||||
Étant donné que je suis sur la page "accueil"
|
||||
Quand je navigue vers "mon profil"
|
||||
Alors je vois l'écran "profile"
|
||||
|
||||
Scénario: Voir mon réseau
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Alors l'écran contient un texte "Amis"
|
||||
Et l'écran contient un texte "Mes amis"
|
||||
|
||||
Scénario: Voir un profil de mon réseau
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Quand je clique sur un participant
|
||||
Alors je vois l'écran "user-profile"
|
||||
|
||||
Scénario: Consulter un événement depuis un profil
|
||||
Étant donné que je suis sur la page "profil utilisateur"
|
||||
Quand je clique sur un événement
|
||||
Alors je vois l'écran "event-detail"
|
||||
|
||||
Scénario: Vérifier les données du profil
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Alors l'écran contient un texte "Événements"
|
||||
Et l'écran contient un texte "Participations"
|
||||
|
||||
Scénario: Voir les profils publiques
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,28 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-21 Décider que tous les utilisateurs puissent suivre mes activités
|
||||
En tant qu'utilisateur
|
||||
Je peux décider que tous les utilisateurs puissent suivre toutes mes activités
|
||||
En déclarant mon profil public
|
||||
Afin de communiquer au sujet de mes déplacements et faire la publicité des événements
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder aux paramètres de profil
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Quand je navigue vers "paramètres"
|
||||
Alors je vois l'écran "settings"
|
||||
|
||||
Scénario: Configurer la visibilité du profil
|
||||
Étant donné que je suis sur la page "paramètres"
|
||||
Alors l'écran contient une section "Confidentialité"
|
||||
|
||||
Scénario: Rendre le profil public
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Vérifier les données des paramètres
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Vérifier les données du profil
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,34 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-22 Parrainer un nouvel utilisateur
|
||||
En tant qu'utilisateur
|
||||
Je peux parrainer un nouvel utilisateur
|
||||
En lui partageant mon QR code ou lien de contact
|
||||
Afin de savoir combien de personnes ont rejoint le réseau grâce à moi
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder au partage de profil
|
||||
Étant donné que je suis sur la page "profil"
|
||||
Alors l'écran contient un bouton "Partager"
|
||||
|
||||
Scénario: Naviguer vers le partage de profil
|
||||
Étant donné que je suis sur la page "profil"
|
||||
Quand je navigue vers "partage de profil"
|
||||
Alors je vois l'écran "share-profile"
|
||||
|
||||
Scénario: Voir le QR code de parrainage
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient un texte "Scannez pour me retrouver sur Festipod"
|
||||
|
||||
Scénario: Voir le lien de parrainage
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient une section "Mon lien de profil"
|
||||
Et l'écran contient un bouton "Copier"
|
||||
|
||||
Scénario: Voir les statistiques de parrainage
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient une section "Statistiques de parrainage"
|
||||
Et l'écran contient un texte "Personnes parrainées"
|
||||
Et l'écran contient un texte "Scans du QR code"
|
||||
@@ -0,0 +1,31 @@
|
||||
# language: fr
|
||||
@USER @priority-1
|
||||
Fonctionnalité: US-23 Me connecter avec d'autres utilisateurs
|
||||
En tant qu'utilisateur
|
||||
Je peux me connecter avec d'autres utilisateurs
|
||||
En partageant mon QR code ou mon lien de contact
|
||||
Afin d'étendre mon réseau
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder au partage depuis le profil
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Alors l'écran contient une section "Partager"
|
||||
|
||||
Scénario: Voir le QR code
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient un texte "Scannez pour me retrouver sur Festipod"
|
||||
|
||||
Scénario: Voir le lien de partage
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient une section "Mon lien de profil"
|
||||
|
||||
Scénario: Accéder à l'écran de partage dédié
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Quand je navigue vers "partage de profil"
|
||||
Alors je vois l'écran "share-profile"
|
||||
|
||||
Scénario: Vérifier les données du profil
|
||||
Étant donné que je suis sur la page "partage profil"
|
||||
Alors l'écran contient un texte "Marie Dupont"
|
||||
@@ -0,0 +1,20 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-24 Être notifié des activités de mes contacts
|
||||
En tant qu'utilisateur
|
||||
Je peux être notifié lorsqu'un contact participe à des événements
|
||||
Afin d'obtenir une synthèse du contenu des ateliers et événements
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder aux paramètres de notification
|
||||
Étant donné que je suis sur la page "paramètres"
|
||||
Alors l'écran contient une section "Notifications"
|
||||
|
||||
Scénario: Configurer les notifications de contacts
|
||||
Étant donné que je suis sur la page "paramètres"
|
||||
Alors je peux configurer mes notifications
|
||||
|
||||
Scénario: Voir les activités de mes contacts sur l'accueil
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,20 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-25 Être averti des événements susceptibles de m'intéresser
|
||||
En tant qu'utilisateur
|
||||
Je peux être notifié lorsqu'un nouvel événement est ajouté près de chez moi
|
||||
Et/ou avec une thématique qui m'intéresse
|
||||
En configurant mes notifications
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder aux paramètres de notification
|
||||
Étant donné que je suis sur la page "paramètres"
|
||||
Alors l'écran contient une section "Notifications"
|
||||
|
||||
Scénario: Configurer le rayon de notification
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Configurer les thématiques d'intérêt
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,31 @@
|
||||
# language: fr
|
||||
@USER @priority-2
|
||||
Fonctionnalité: US-26 Définir la portée d'un événement
|
||||
En tant qu'utilisateur
|
||||
Je peux relayer/présenter le contenu d'un événement et le catégoriser par type/thématique
|
||||
En indiquant son rayon d'intérêt en kilomètres
|
||||
Afin de m'assurer que les utilisateurs qui habitent trop loin ne reçoivent pas de notification
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder au formulaire de relai d'événement
|
||||
Étant donné que je suis sur la page "accueil"
|
||||
Quand je navigue vers "relayer un événement"
|
||||
Alors je vois l'écran "create-event"
|
||||
|
||||
Scénario: Définir le rayon d'intérêt
|
||||
* Scénario non implémenté
|
||||
|
||||
Scénario: Choisir une thématique
|
||||
Étant donné que je suis sur la page "relayer un événement"
|
||||
Alors l'écran contient une section "Thématique"
|
||||
|
||||
Scénario: Vérifier les champs obligatoires
|
||||
Étant donné que l'écran "create-event" est affiché
|
||||
Alors le formulaire contient les champs obligatoires suivants:
|
||||
| Nom de l'événement |
|
||||
| Date de début |
|
||||
| Heure de début |
|
||||
| Lieu |
|
||||
| Thématique |
|
||||
@@ -0,0 +1,32 @@
|
||||
# language: fr
|
||||
@USER @priority-0
|
||||
Fonctionnalité: US-9 Visualiser la photo d'un individu
|
||||
En tant qu'utilisateur
|
||||
Je peux visualiser la photo d'un individu ou ajouter une photo personnelle sur une fiche existante
|
||||
Et consulter la liste des inscrits à un atelier
|
||||
Afin d'identifier les personnes que j'ai rencontrées dont je n'ai pas noté leur nom
|
||||
|
||||
Contexte:
|
||||
Étant donné que je suis connecté en tant qu'utilisateur
|
||||
|
||||
Scénario: Accéder au profil pour voir la photo
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Alors l'écran contient un avatar
|
||||
|
||||
Scénario: Naviguer vers le profil depuis la liste des participants
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Quand je clique sur un participant
|
||||
Alors je suis redirigé vers "profil utilisateur"
|
||||
Et l'écran affiche les informations du profil
|
||||
|
||||
Scénario: Consulter la liste des inscrits à un atelier
|
||||
Étant donné que je suis sur la page "détail événement"
|
||||
Alors je peux voir la liste des participants
|
||||
|
||||
Scénario: Vérifier les champs de données du profil
|
||||
Étant donné que je suis sur la page "mon profil"
|
||||
Alors l'écran contient un texte "Marie Dupont"
|
||||
Et l'écran contient un texte "@mariedupont"
|
||||
|
||||
Scénario: Ajouter une photo personnelle sur une fiche existante
|
||||
* Scénario non implémenté
|
||||
@@ -0,0 +1,106 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Header, Text, Avatar, Input, Button, Badge } from '../../../shared/components/sketchy';
|
||||
import { useFestipodData } from '../../../shared/context/FestipodDataContext';
|
||||
import type { ScreenProps } from '../../../screens';
|
||||
|
||||
export function FriendsListScreen({ navigate }: ScreenProps) {
|
||||
const { getFriends, users, setSelectedUserId } = useFestipodData();
|
||||
const [activeTab, setActiveTab] = useState<'friends' | 'public'>('friends');
|
||||
|
||||
const friends = getFriends();
|
||||
const publicProfiles = users.filter(u => u.isPublic);
|
||||
|
||||
const displayedList = activeTab === 'friends' ? friends : publicProfiles;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Header
|
||||
title="Mon réseau"
|
||||
left={<span onClick={() => navigate('profile')} style={{ cursor: 'pointer' }}>←</span>}
|
||||
/>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', borderBottom: '2px solid var(--sketch-black)' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('friends')}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '12px 16px',
|
||||
background: activeTab === 'friends' ? 'var(--sketch-light-gray)' : 'transparent',
|
||||
border: 'none',
|
||||
borderBottom: activeTab === 'friends' ? '3px solid var(--sketch-black)' : '3px solid transparent',
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 14,
|
||||
fontWeight: activeTab === 'friends' ? 'bold' : 'normal',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Mes amis ({friends.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('public')}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '12px 16px',
|
||||
background: activeTab === 'public' ? 'var(--sketch-light-gray)' : 'transparent',
|
||||
border: 'none',
|
||||
borderBottom: activeTab === 'public' ? '3px solid var(--sketch-black)' : '3px solid transparent',
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 14,
|
||||
fontWeight: activeTab === 'public' ? 'bold' : 'normal',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
Profils publics
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search bar */}
|
||||
<div style={{ padding: 16, borderBottom: '1px solid var(--sketch-light-gray)' }}>
|
||||
<Input placeholder="Rechercher..." />
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{displayedList.map((person) => (
|
||||
<div
|
||||
key={person.id}
|
||||
onClick={() => { setSelectedUserId(person.id); navigate('user-profile'); }}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '12px 16px',
|
||||
cursor: 'pointer',
|
||||
borderBottom: '1px solid var(--sketch-light-gray)',
|
||||
}}
|
||||
>
|
||||
<Avatar initials={person.initials} size="sm" />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{person.name}</Text>
|
||||
{person.role && <Badge>{person.role}</Badge>}
|
||||
</div>
|
||||
<Text style={{ margin: 0, fontSize: 13 }}>
|
||||
<span className="user-content">{person.username}</span>
|
||||
{person.eventsCount != null && (
|
||||
<span style={{ color: 'var(--sketch-gray)' }}> · {person.eventsCount} événements</span>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<Text style={{ margin: 0, fontSize: 20, color: 'var(--sketch-gray)' }}>›</Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add friend button */}
|
||||
{activeTab === 'friends' && (
|
||||
<div style={{ padding: 16, borderTop: '2px solid var(--sketch-black)' }}>
|
||||
<Button variant="primary" style={{ width: '100%' }}>
|
||||
+ Ajouter un ami
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { Header, Avatar, Title, Text, Button, Card, Badge, Divider, NavBar } from '../../../shared/components/sketchy';
|
||||
import { useFestipodData } from '../../../shared/context/FestipodDataContext';
|
||||
import type { ScreenProps } from '../../../screens';
|
||||
|
||||
export function ProfileScreen({ navigate }: ScreenProps) {
|
||||
const { currentUser, getUserEvents, currentUserId, getFriends, setSelectedEventId } = useFestipodData();
|
||||
const myEvents = getUserEvents(currentUserId);
|
||||
const friends = getFriends();
|
||||
|
||||
const user = currentUser;
|
||||
|
||||
const handleEventClick = (eventId: string) => {
|
||||
setSelectedEventId(eventId);
|
||||
navigate('event-detail');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Header
|
||||
title="Mon profil"
|
||||
left={<span onClick={() => navigate('home')} style={{ cursor: 'pointer' }}>←</span>}
|
||||
right={<span onClick={() => navigate('settings')} style={{ cursor: 'pointer' }}>⚙</span>}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{/* Profile header */}
|
||||
<div style={{ padding: 24, textAlign: 'center' }}>
|
||||
<Avatar initials={user?.initials ?? '?'} size="lg" />
|
||||
<Title className="user-content" style={{ marginTop: 16, marginBottom: 4 }}>{user?.name}</Title>
|
||||
<Text className="user-content" style={{ margin: 0 }}>{user?.username}</Text>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: 32, marginTop: 20 }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{user?.eventsCount ?? myEvents.length}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Événements</Text>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center', cursor: 'pointer' }} onClick={() => navigate('friends-list')}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{user?.friendsCount ?? friends.length}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Amis</Text>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{user?.participationsCount ?? 0}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Participations</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 20, justifyContent: 'center' }}>
|
||||
<Button variant="primary" onClick={() => navigate('update-profile')}>Modifier le profil</Button>
|
||||
<Button onClick={() => navigate('share-profile')}>Partager</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Upcoming events */}
|
||||
<div style={{ padding: 16 }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Mes événements à venir</Text>
|
||||
{myEvents.slice(0, 3).map((event) => (
|
||||
<Card key={event.id} onClick={() => handleEventClick(event.id)} style={{ marginBottom: 12 }}>
|
||||
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{event.title}</Text>
|
||||
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>
|
||||
{event.date}
|
||||
</Text>
|
||||
</Card>
|
||||
))}
|
||||
<Button style={{ width: '100%' }} onClick={() => navigate('events')}>
|
||||
Voir tous les événements
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Quick actions */}
|
||||
<div style={{ padding: '0 16px 16px' }}>
|
||||
<div
|
||||
className="sketchy-list-item"
|
||||
onClick={() => navigate('create-event')}
|
||||
>
|
||||
<span style={{ marginRight: 12 }}>+</span>
|
||||
<Text style={{ margin: 0 }}>Relayer un événement</Text>
|
||||
</div>
|
||||
<div className="sketchy-list-item" onClick={() => navigate('friends-list')}>
|
||||
<span style={{ marginRight: 12 }}>👥</span>
|
||||
<Text style={{ margin: 0 }}>Mes amis</Text>
|
||||
</div>
|
||||
<div className="sketchy-list-item">
|
||||
<span style={{ marginRight: 12 }}>📜</span>
|
||||
<Text style={{ margin: 0 }}>Événements passés</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Nav */}
|
||||
<NavBar
|
||||
items={[
|
||||
{ icon: '⌂', label: 'Accueil', onClick: () => navigate('home') },
|
||||
{ icon: '◎', label: 'Découvrir', onClick: () => navigate('events') },
|
||||
{ icon: '+', label: 'Relayer', onClick: () => navigate('create-event') },
|
||||
{ icon: '☺', label: 'Profil', active: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import { Header, Text, Button, Card, Divider, Avatar } from '../../../shared/components/sketchy';
|
||||
import { useFestipodData } from '../../../shared/context/FestipodDataContext';
|
||||
import type { ScreenProps } from '../../../screens';
|
||||
|
||||
export function ShareProfileScreen({ navigate }: ScreenProps) {
|
||||
const { currentUser } = useFestipodData();
|
||||
const user = currentUser;
|
||||
const profileLink = `festipod.app/u/${(user?.username ?? '').replace('@', '')}`;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Header
|
||||
title="Partager mon profil"
|
||||
left={<span onClick={() => navigate('profile')} style={{ cursor: 'pointer' }}>←</span>}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
|
||||
{/* QR Code */}
|
||||
<Card style={{ textAlign: 'center', padding: 24 }}>
|
||||
<div style={{
|
||||
width: 180,
|
||||
height: 180,
|
||||
margin: '0 auto 16px',
|
||||
border: '3px solid var(--sketch-black)',
|
||||
borderRadius: 12,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'var(--sketch-white)',
|
||||
position: 'relative',
|
||||
}}>
|
||||
{/* Simulated QR code pattern */}
|
||||
<div style={{
|
||||
width: 150,
|
||||
height: 150,
|
||||
background: `
|
||||
linear-gradient(90deg, var(--sketch-black) 10%, transparent 10%, transparent 20%, var(--sketch-black) 20%, var(--sketch-black) 30%, transparent 30%, transparent 40%, var(--sketch-black) 40%, var(--sketch-black) 50%, transparent 50%, transparent 60%, var(--sketch-black) 60%, var(--sketch-black) 70%, transparent 70%, transparent 80%, var(--sketch-black) 80%, var(--sketch-black) 90%, transparent 90%),
|
||||
linear-gradient(var(--sketch-black) 10%, transparent 10%, transparent 20%, var(--sketch-black) 20%, var(--sketch-black) 30%, transparent 30%, transparent 40%, var(--sketch-black) 40%, var(--sketch-black) 50%, transparent 50%, transparent 60%, var(--sketch-black) 60%, var(--sketch-black) 70%, transparent 70%, transparent 80%, var(--sketch-black) 80%, var(--sketch-black) 90%, transparent 90%)
|
||||
`,
|
||||
backgroundSize: '15px 15px',
|
||||
opacity: 0.8,
|
||||
}} />
|
||||
{/* Center avatar */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
background: 'var(--sketch-white)',
|
||||
padding: 4,
|
||||
borderRadius: '50%',
|
||||
}}>
|
||||
<Avatar initials={user?.initials ?? '?'} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Text className="user-content" style={{ fontWeight: 'bold', margin: '0 0 4px 0' }}>{user?.name}</Text>
|
||||
<Text style={{ color: 'var(--sketch-gray)', margin: 0, fontSize: 14 }}>
|
||||
Scannez pour me retrouver sur Festipod
|
||||
</Text>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Link section */}
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Mon lien de profil</Text>
|
||||
<Card style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Text style={{
|
||||
margin: 0,
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{profileLink}
|
||||
</Text>
|
||||
<Button style={{ flexShrink: 0, padding: '8px 10px' }} title="Copier le lien">📋</Button>
|
||||
<Button variant="primary" style={{ flexShrink: 0, padding: '8px 10px' }} title="Partager">↗</Button>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Stats */}
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Statistiques de parrainage</Text>
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-around', textAlign: 'center' }}>
|
||||
<div>
|
||||
<Text style={{ fontWeight: 'bold', fontSize: 24, margin: 0, color: 'var(--sketch-black)' }}>12</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>
|
||||
Personnes parrainées
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text style={{ fontWeight: 'bold', fontSize: 24, margin: 0, color: 'var(--sketch-black)' }}>47</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>
|
||||
Scans du QR code
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Header, Text, Input, Button, Avatar } from '../../../shared/components/sketchy';
|
||||
import { useFestipodData } from '../../../shared/context/FestipodDataContext';
|
||||
import type { ScreenProps } from '../../../screens';
|
||||
|
||||
export function UpdateProfileScreen({ navigate }: ScreenProps) {
|
||||
const { currentUser, updateProfile } = useFestipodData();
|
||||
const user = currentUser;
|
||||
|
||||
const nameParts = (user?.name ?? '').split(' ');
|
||||
const [firstName, setFirstName] = useState(nameParts[0] ?? '');
|
||||
const [lastName, setLastName] = useState(nameParts.slice(1).join(' '));
|
||||
const [username, setUsername] = useState(user?.username ?? '');
|
||||
const [city, setCity] = useState(user?.city ?? 'Lyon, France');
|
||||
const [bio, setBio] = useState(user?.bio ?? '');
|
||||
|
||||
const handleSave = () => {
|
||||
const fullName = `${firstName} ${lastName}`.trim();
|
||||
const initials = `${firstName[0] ?? ''}${lastName[0] ?? ''}`.toUpperCase();
|
||||
updateProfile({
|
||||
name: fullName,
|
||||
initials,
|
||||
username,
|
||||
city,
|
||||
bio,
|
||||
});
|
||||
navigate('profile');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Header
|
||||
title="Modifier le profil"
|
||||
left={<span onClick={() => navigate('profile')} style={{ cursor: 'pointer' }}>✕</span>}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ flex: 1, padding: 16, overflow: 'auto' }}>
|
||||
{/* Photo */}
|
||||
<div style={{ textAlign: 'center', marginBottom: 24 }}>
|
||||
<Avatar initials={user?.initials ?? '?'} size="lg" />
|
||||
<Button style={{ marginTop: 12 }}>
|
||||
Changer la photo
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div>
|
||||
<Text style={{ marginBottom: 6, fontSize: 14 }}>Prénom *</Text>
|
||||
<Input value={firstName} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFirstName(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text style={{ marginBottom: 6, fontSize: 14 }}>Nom *</Text>
|
||||
<Input value={lastName} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setLastName(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text style={{ marginBottom: 6, fontSize: 14 }}>Pseudo</Text>
|
||||
<Input value={username} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text style={{ marginBottom: 6, fontSize: 14 }}>Localisation</Text>
|
||||
<Input value={city} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setCity(e.target.value)} placeholder="Ville, Pays" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text style={{ marginBottom: 6, fontSize: 14 }}>Bio</Text>
|
||||
<textarea
|
||||
className="sketchy-input"
|
||||
value={bio}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setBio(e.target.value)}
|
||||
rows={3}
|
||||
style={{ resize: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div style={{ padding: 16, borderTop: '2px solid var(--sketch-black)' }}>
|
||||
<Button
|
||||
variant="primary"
|
||||
style={{ width: '100%' }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Enregistrer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import React from 'react';
|
||||
import { Header, Avatar, Title, Text, Button, Card, Badge, Divider } from '../../../shared/components/sketchy';
|
||||
import { useFestipodData } from '../../../shared/context/FestipodDataContext';
|
||||
import type { ScreenProps } from '../../../screens';
|
||||
|
||||
export function UserProfileScreen({ navigate }: ScreenProps) {
|
||||
const { users, currentUserId, selectedUser, getUserEvents, addFriend, getFriends, setSelectedEventId } = useFestipodData();
|
||||
|
||||
// Use selectedUser from context, fallback to first non-current user
|
||||
const viewedUser = selectedUser
|
||||
|| users.find(u => u.id !== currentUserId);
|
||||
|
||||
const friends = getFriends();
|
||||
const isFriend = viewedUser ? friends.some(f => f.id === viewedUser.id) : false;
|
||||
const userEvents = viewedUser ? getUserEvents(viewedUser.id) : [];
|
||||
|
||||
// Hardcoded past events for this mockup view (not all in store yet)
|
||||
const pastEvents = [
|
||||
{ title: 'Forum Ouvert Transition', date: '22 jan.', location: "Tiers-lieu L'Hermitage", distance: 89, common: true },
|
||||
{ title: 'Rencontre des Colibris', date: '12 jan.', location: "La Maison de l'Environnement", distance: 7, common: true },
|
||||
{ title: 'Formation CNV', date: '8 jan.', location: 'MJC Montplaisir, Lyon', distance: 5, common: false },
|
||||
{ title: 'Café des possibles', date: '15 déc.', location: 'Café de la Mairie, Lyon 3', distance: 2, common: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Header
|
||||
title="Profil"
|
||||
left={<span onClick={() => navigate('event-detail')} style={{ cursor: 'pointer' }}>←</span>}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{/* User profile header */}
|
||||
<div style={{ padding: 24, textAlign: 'center' }}>
|
||||
<Avatar initials={viewedUser?.initials ?? '?'} size="lg" />
|
||||
<Title className="user-content" style={{ marginTop: 16, marginBottom: 4 }}>{viewedUser?.name}</Title>
|
||||
<Text className="user-content" style={{ margin: 0 }}>{viewedUser?.username}</Text>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: 32, marginTop: 20 }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{viewedUser?.eventsCount ?? userEvents.length}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Événements</Text>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{viewedUser?.friendsCount ?? 23}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Contacts</Text>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Text style={{ fontWeight: 'bold', margin: 0 }}>{viewedUser?.participationsCount ?? 42}</Text>
|
||||
<Text style={{ fontSize: 12, color: 'var(--sketch-gray)', margin: 0 }}>Participations</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 20, justifyContent: 'center' }}>
|
||||
<Button
|
||||
variant={isFriend ? 'default' : 'primary'}
|
||||
onClick={() => viewedUser && addFriend(viewedUser.id)}
|
||||
>
|
||||
{isFriend ? '✓ Dans mon réseau' : 'Ajouter au réseau'}
|
||||
</Button>
|
||||
<Button>Contacter</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Upcoming events from store */}
|
||||
<div style={{ padding: 16 }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Événements à venir</Text>
|
||||
|
||||
{(userEvents.length > 0 ? userEvents : [
|
||||
{ id: 'event-1', title: 'Résidence Reconnexion', date: '16-20 fév.', location: 'Le Revel, Rogues (30)', distance: 142 },
|
||||
]).map((event) => (
|
||||
<Card key={event.id} onClick={() => { setSelectedEventId(event.id); navigate('event-detail'); }} style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{event.title}</Text>
|
||||
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>
|
||||
{event.date}
|
||||
</Text>
|
||||
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
|
||||
📍 <span className="user-content">{event.location}</span>
|
||||
{event.distance != null && (
|
||||
<span style={{ color: 'var(--sketch-gray)' }}> · {event.distance} km</span>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge>moi aussi</Badge>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Past events (still hardcoded for mockup) */}
|
||||
<div style={{ padding: 16 }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Événements passés</Text>
|
||||
|
||||
{pastEvents.map((event, i) => (
|
||||
<Card key={i} onClick={() => navigate('event-detail')} style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{event.title}</Text>
|
||||
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>
|
||||
{event.date}
|
||||
</Text>
|
||||
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
|
||||
📍 <span className="user-content">{event.location}</span>
|
||||
<span style={{ color: 'var(--sketch-gray)' }}> · {event.distance} km</span>
|
||||
</Text>
|
||||
</div>
|
||||
{event.common && <Badge>moi aussi</Badge>}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Contact form section */}
|
||||
<div style={{ padding: 16 }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 12 }}>Envoyer un message</Text>
|
||||
<div style={{
|
||||
border: '2px solid var(--sketch-black)',
|
||||
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
|
||||
padding: 12,
|
||||
minHeight: 80,
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 14,
|
||||
color: 'var(--sketch-gray)',
|
||||
}}>
|
||||
Écrivez votre message ici...
|
||||
</div>
|
||||
<Button variant="primary" style={{ width: '100%', marginTop: 12 }}>
|
||||
Envoyer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from 'chai';
|
||||
import type { FestipodWorld } from '../../../../shared/support/world';
|
||||
|
||||
When('je clique sur un participant', async function (this: FestipodWorld) {
|
||||
this.navigateTo('#/demo/user-profile');
|
||||
});
|
||||
|
||||
Given('je visualise le profil de {string}', async function (this: FestipodWorld, userName: string) {
|
||||
this.navigateTo('#/demo/user-profile');
|
||||
expect(this.currentScreen, 'User profile screen should be loaded').to.not.be.null;
|
||||
this.attach(`Viewing profile: ${userName}`, 'text/plain');
|
||||
});
|
||||
|
||||
Then('je peux voir mon profil', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('profile');
|
||||
const source = this.getRenderedText();
|
||||
expect(/<Avatar[^>]*initials="MD"[^>]*size="lg"/.test(source), 'Profile should have Avatar with initials="MD" and size="lg"').to.be.true;
|
||||
expect(/<Title[^>]*>Marie Dupont<\/Title>/.test(source), 'Profile should have Title "Marie Dupont"').to.be.true;
|
||||
expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir le profil de l\'utilisateur', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
const source = this.getRenderedText();
|
||||
expect(/<Avatar[^>]*initials="JD"[^>]*size="lg"/.test(source), 'User profile should have Avatar with initials="JD" and size="lg"').to.be.true;
|
||||
expect(/<Title[^>]*>Jean Durand<\/Title>/.test(source), 'User profile should have Title "Jean Durand"').to.be.true;
|
||||
expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;
|
||||
});
|
||||
|
||||
Then('l\'écran affiche les informations du profil', async function (this: FestipodWorld) {
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'profile') {
|
||||
expect(/<Avatar[^>]*initials="MD"/.test(source), 'Profile should have Avatar with initials="MD"').to.be.true;
|
||||
expect(/<Title[^>]*>Marie Dupont<\/Title>/.test(source), 'Profile should have Title "Marie Dupont"').to.be.true;
|
||||
expect(/@mariedupont/.test(source), 'Profile should have username @mariedupont').to.be.true;
|
||||
} else if (this.currentScreenId === 'user-profile') {
|
||||
expect(/<Avatar[^>]*initials="JD"/.test(source), 'User profile should have Avatar with initials="JD"').to.be.true;
|
||||
expect(/<Title[^>]*>Jean Durand<\/Title>/.test(source), 'User profile should have Title "Jean Durand"').to.be.true;
|
||||
expect(/@jeandurand/.test(source), 'User profile should have username @jeandurand').to.be.true;
|
||||
} else {
|
||||
expect.fail(`Unexpected screen "${this.currentScreenId}" for profile info check`);
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux contacter l\'utilisateur', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
const source = this.getRenderedText();
|
||||
const hasContactButton = /<Button>Contacter<\/Button>/.test(source);
|
||||
expect(hasContactButton, 'User profile should have "Contacter" button').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir les événements auxquels l\'utilisateur a participé', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
const source = this.getRenderedText();
|
||||
expect(/Événements à venir/.test(source), 'User profile should have "Événements à venir" section').to.be.true;
|
||||
expect(/Événements passés/.test(source), 'User profile should have "Événements passés" section').to.be.true;
|
||||
});
|
||||
|
||||
Then('les événements affichent leur localisation et distance', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId).to.equal('user-profile');
|
||||
const source = this.getRenderedText();
|
||||
expect(/location: '[^']+'/.test(source), 'Events should have location data').to.be.true;
|
||||
expect(/distance: \d+/.test(source), 'Events should have distance data').to.be.true;
|
||||
expect(/\{event\.location\}/.test(source), 'Events should render location').to.be.true;
|
||||
expect(/\{event\.distance\}/.test(source), 'Events should render distance').to.be.true;
|
||||
});
|
||||
|
||||
Then('je peux voir le QR code', async function (this: FestipodWorld) {
|
||||
const source = this.getRenderedText();
|
||||
if (this.currentScreenId === 'share-profile') {
|
||||
expect(/QR Code/.test(source), 'Share profile should have "QR Code" text').to.be.true;
|
||||
expect(/Scannez pour me retrouver/.test(source), 'Share profile should have "Scannez pour me retrouver" text').to.be.true;
|
||||
} else if (this.currentScreenId === 'meeting-points') {
|
||||
expect(/Mon QR Code/.test(source), 'Meeting points should have "Mon QR Code" text').to.be.true;
|
||||
expect(/Scannez pour m'ajouter/.test(source), 'Meeting points should have "Scannez pour m\'ajouter" text').to.be.true;
|
||||
} else {
|
||||
expect.fail(`QR code should be on share-profile or meeting-points, not "${this.currentScreenId}"`);
|
||||
}
|
||||
});
|
||||
|
||||
Then('je peux voir le lien de partage', async function (this: FestipodWorld) {
|
||||
expect(this.currentScreenId, 'Share link should be on share-profile screen').to.equal('share-profile');
|
||||
const source = this.getRenderedText();
|
||||
expect(/Mon lien de profil/.test(source), 'Share profile should have "Mon lien de profil" text').to.be.true;
|
||||
expect(/festipod\.app\/u\//.test(source), 'Share profile should have profile link URL').to.be.true;
|
||||
});
|
||||
Reference in New Issue
Block a user