-
-
Marie Dupont
-
@mariedupont
+
+
{user?.name}
+
{user?.username}
- 12
+ {user?.eventsCount ?? myEvents.length}
Événements
navigate('friends-list')}>
- 48
+ {user?.friendsCount ?? friends.length}
Amis
- 156
+ {user?.participationsCount ?? 0}
Participations
@@ -50,8 +57,8 @@ export function ProfileScreen({ navigate }: ScreenProps) {
{/* Upcoming events */}
Mes événements à venir
- {upcomingEvents.map((event, i) => (
-
navigate('event-detail')} style={{ marginBottom: 12 }}>
+ {myEvents.slice(0, 3).map((event) => (
+ handleEventClick(event.id)} style={{ marginBottom: 12 }}>
{event.title}
{event.date}
diff --git a/src/screens/ShareProfileScreen.tsx b/src/modules/user/screens/ShareProfileScreen.tsx
similarity index 90%
rename from src/screens/ShareProfileScreen.tsx
rename to src/modules/user/screens/ShareProfileScreen.tsx
index 346b21b..8d5059b 100644
--- a/src/screens/ShareProfileScreen.tsx
+++ b/src/modules/user/screens/ShareProfileScreen.tsx
@@ -1,9 +1,12 @@
import React from 'react';
-import { Header, Text, Button, Card, Divider, Avatar } from '../components/sketchy';
-import type { ScreenProps } from './index';
+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 profileLink = 'festipod.app/u/mariedupont';
+ const { currentUser } = useFestipodData();
+ const user = currentUser;
+ const profileLink = `festipod.app/u/${(user?.username ?? '').replace('@', '')}`;
return (
@@ -46,11 +49,11 @@ export function ShareProfileScreen({ navigate }: ScreenProps) {
padding: 4,
borderRadius: '50%',
}}>
-
+
-
Marie Dupont
+
{user?.name}
Scannez pour me retrouver sur Festipod
diff --git a/src/modules/user/screens/UpdateProfileScreen.tsx b/src/modules/user/screens/UpdateProfileScreen.tsx
new file mode 100644
index 0000000..a878917
--- /dev/null
+++ b/src/modules/user/screens/UpdateProfileScreen.tsx
@@ -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 (
+
+
navigate('profile')} style={{ cursor: 'pointer' }}>✕}
+ />
+
+ {/* Content */}
+
+ {/* Photo */}
+
+
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+ );
+}
diff --git a/src/screens/UserProfileScreen.tsx b/src/modules/user/screens/UserProfileScreen.tsx
similarity index 66%
rename from src/screens/UserProfileScreen.tsx
rename to src/modules/user/screens/UserProfileScreen.tsx
index 613930f..97aca90 100644
--- a/src/screens/UserProfileScreen.tsx
+++ b/src/modules/user/screens/UserProfileScreen.tsx
@@ -1,16 +1,23 @@
import React from 'react';
-import { Header, Avatar, Title, Text, Button, Card, Badge, Divider } from '../components/sketchy';
-import type { ScreenProps } from './index';
+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 upcomingEvents = [
- { title: 'Résidence Reconnexion', date: '16-20 fév.', location: 'Le Revel, Rogues (30)', distance: 142, common: true },
- { title: 'Atelier permaculture', date: '28 fév.', location: 'Ferme des Music, Vénissieux', distance: 12, common: false },
- ];
+ 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: '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 },
];
@@ -26,39 +33,46 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
{/* User profile header */}
-
-
Jean Durand
-
@jeandurand
+
+
{viewedUser?.name}
+
{viewedUser?.username}
- 8
+ {viewedUser?.eventsCount ?? userEvents.length}
Événements
- 23
+ {viewedUser?.friendsCount ?? 23}
Contacts
- 42
+ {viewedUser?.participationsCount ?? 42}
Participations
-
+
- {/* Upcoming events */}
+ {/* Upcoming events from store */}
Événements à venir
- {upcomingEvents.map((event, i) => (
-
navigate('event-detail')} style={{ marginBottom: 12 }}>
+ {(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) => (
+ { setSelectedEventId(event.id); navigate('event-detail'); }} style={{ marginBottom: 12 }}>
{event.title}
@@ -67,10 +81,12 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
📍 {event.location}
- · {event.distance} km
+ {event.distance != null && (
+ · {event.distance} km
+ )}
- {event.common &&
moi aussi}
+
moi aussi
))}
@@ -78,7 +94,7 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
- {/* Past events */}
+ {/* Past events (still hardcoded for mockup) */}
Événements passés
diff --git a/src/modules/user/steps/frontend/user.steps.ts b/src/modules/user/steps/frontend/user.steps.ts
new file mode 100644
index 0000000..3e517d4
--- /dev/null
+++ b/src/modules/user/steps/frontend/user.steps.ts
@@ -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(/
]*initials="MD"[^>]*size="lg"/.test(source), 'Profile should have Avatar with initials="MD" and size="lg"').to.be.true;
+ expect(/]*>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(/]*initials="JD"[^>]*size="lg"/.test(source), 'User profile should have Avatar with initials="JD" and size="lg"').to.be.true;
+ expect(/]*>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(/]*initials="MD"/.test(source), 'Profile should have Avatar with initials="MD"').to.be.true;
+ expect(/]*>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(/]*initials="JD"/.test(source), 'User profile should have Avatar with initials="JD"').to.be.true;
+ expect(/]*>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 = /