Suggest on creation + distance in kms

This commit is contained in:
Sylvain Duchesne
2026-01-26 18:32:16 +01:00
parent b7f86b139f
commit c9bc957d2a
14 changed files with 1236 additions and 657 deletions
+58 -2
View File
@@ -179,6 +179,48 @@ export const parsedFeatures: ParsedFeature[] = [
}
]
},
{
"name": "Détecter un événement similaire déjà relayé",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "le formulaire permet de détecter les doublons"
}
]
},
{
"name": "Importer un événement depuis une source externe",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "le formulaire permet d'importer depuis Mobilizon ou Transiscope"
}
]
},
{
"name": "Pas d'alerte doublon lors d'un import externe",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "l'écran \"create-event\" est affiché"
},
{
"keyword": "Alors",
"text": "l'import externe ne déclenche pas d'alerte doublon"
}
]
},
{
"name": "Modifier un événement",
"tags": [],
@@ -196,7 +238,7 @@ export const parsedFeatures: ParsedFeature[] = [
}
],
"filePath": "features/event/us-13-creer-evenement.feature",
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Relayer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux relayer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de relayer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au formulaire de relai d'événement\n Étant donné que je suis sur la page \"accueil\"\n Quand je navigue vers \"relayer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date de début |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Vérifier la présence du bouton de relai\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors l'écran contient une section \"Relayer l'événement\"\n\n Scénario: Pouvoir annuler le relai d'événement\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors je peux annuler et revenir à l'écran précédent\n\n Scénario: Modifier un événement\n * Scénario non implémenté\n\n Scénario: Supprimer un événement\n * Scénario non implémenté\n\n Scénario: Retirer une organisation (personne ou structure)\n * Scénario non implémenté\n",
"rawContent": "# language: fr\n@EVENT @priority-1\nFonctionnalité: US-13 Relayer/Modifier/Supprimer un événement\n En tant qu'utilisateur\n Je peux relayer/modifier/supprimer un événement\n En choisissant les dates, horaires, lieu et thématique\n Afin de relayer/présenter le contenu de cet événement et le catégoriser\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au formulaire de relai d'événement\n Étant donné que je suis sur la page \"accueil\"\n Quand je navigue vers \"relayer un événement\"\n Alors je vois l'écran \"create-event\"\n\n Scénario: Vérifier les champs obligatoires du formulaire\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire contient les champs obligatoires suivants:\n | Nom de l'événement |\n | Date de début |\n | Heure de début |\n | Lieu |\n | Thématique |\n\n Scénario: Vérifier la présence du bouton de relai\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors l'écran contient une section \"Relayer l'événement\"\n\n Scénario: Pouvoir annuler le relai d'événement\n Étant donné que je suis sur la page \"relayer un événement\"\n Alors je peux annuler et revenir à l'écran précédent\n\n Scénario: Détecter un événement similaire déjà relayé\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire permet de détecter les doublons\n\n Scénario: Importer un événement depuis une source externe\n Étant donné que l'écran \"create-event\" est affiché\n Alors le formulaire permet d'importer depuis Mobilizon ou Transiscope\n\n Scénario: Pas d'alerte doublon lors d'un import externe\n Étant donné que l'écran \"create-event\" est affiché\n Alors l'import externe ne déclenche pas d'alerte doublon\n\n Scénario: Modifier un événement\n * Scénario non implémenté\n\n Scénario: Supprimer un événement\n * Scénario non implémenté\n\n Scénario: Retirer une organisation (personne ou structure)\n * Scénario non implémenté\n",
"screenIds": [
"create-event",
"home"
@@ -500,6 +542,20 @@ export const parsedFeatures: ParsedFeature[] = [
}
]
},
{
"name": "Voir la localisation des événements",
"tags": [],
"steps": [
{
"keyword": "Étant donné que ",
"text": "je suis sur la page \"profil utilisateur\""
},
{
"keyword": "Alors",
"text": "les événements affichent leur localisation et distance"
}
]
},
{
"name": "Voir le formulaire de contact",
"tags": [],
@@ -535,7 +591,7 @@ export const parsedFeatures: ParsedFeature[] = [
}
],
"filePath": "features/user/us-10-profil-participant.feature",
"rawContent": "# language: fr\n@USER @priority-1\nFonctionnalité: US-10 Visualiser la fiche/le profil d'un participant\n En tant qu'utilisateur\n Je peux sélectionner un individu dans la liste des inscrits à un événement/atelier\n Afin de voir les événements auxquels la personne a participé et voir un formulaire de contact\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au profil d'un participant\n Étant donné que je suis sur la page \"détail événement\"\n Quand je clique sur un participant\n Alors je vois l'écran \"user-profile\"\n\n Scénario: Voir les événements du participant\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux voir les événements auxquels l'utilisateur a participé\n\n Scénario: Voir le formulaire de contact\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux contacter l'utilisateur\n\n Scénario: Vérifier les informations du profil\n * Scénario non implémenté\n\n Scénario: Voir les détails du profil utilisateur\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors l'écran affiche les informations du profil\n",
"rawContent": "# language: fr\n@USER @priority-1\nFonctionnalité: US-10 Visualiser la fiche/le profil d'un participant\n En tant qu'utilisateur\n Je peux sélectionner un individu dans la liste des inscrits à un événement/atelier\n Afin de voir les événements auxquels la personne a participé et voir un formulaire de contact\n\n Contexte:\n Étant donné que je suis connecté en tant qu'utilisateur\n\n Scénario: Accéder au profil d'un participant\n Étant donné que je suis sur la page \"détail événement\"\n Quand je clique sur un participant\n Alors je vois l'écran \"user-profile\"\n\n Scénario: Voir les événements du participant\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux voir les événements auxquels l'utilisateur a participé\n\n Scénario: Voir la localisation des événements\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors les événements affichent leur localisation et distance\n\n Scénario: Voir le formulaire de contact\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors je peux contacter l'utilisateur\n\n Scénario: Vérifier les informations du profil\n * Scénario non implémenté\n\n Scénario: Voir les détails du profil utilisateur\n Étant donné que je suis sur la page \"profil utilisateur\"\n Alors l'écran affiche les informations du profil\n",
"screenIds": [
"event-detail",
"user-profile"
+29 -1
View File
@@ -206,6 +206,27 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"sourceCode": "Then('le champ {string} est présent', async function (this: FestipodWorld, fieldName: string) {\n const source = this.getRenderedText();\n // Check that field label exists in screen source\n const escapedName = fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`>${escapedName}[^<]*<`);\n expect(pattern.test(source), `Field \"${fieldName}\" should be present in screen`).to.be.true;\n});",
"lineNumber": 53
},
{
"pattern": "le formulaire permet de détecter les doublons",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('le formulaire permet de détecter les doublons', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: showDuplicateWarning logic and \"Événement similaire détecté\" warning\n expect(/showDuplicateWarning/.test(source), 'Form should have duplicate detection logic').to.be.true;\n expect(/Événement similaire détecté/.test(source), 'Form should have duplicate warning message').to.be.true;\n});",
"lineNumber": 64
},
{
"pattern": "le formulaire permet d'importer depuis Mobilizon ou Transiscope",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('le formulaire permet d\\'importer depuis Mobilizon ou Transiscope', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: importableEvents with Mobilizon and Transiscope sources\n expect(/importableEvents/.test(source), 'Form should have importable events data').to.be.true;\n expect(/Mobilizon/.test(source), 'Form should support Mobilizon import').to.be.true;\n expect(/Transiscope/.test(source), 'Form should support Transiscope import').to.be.true;\n expect(/Importer depuis une source externe/.test(source), 'Form should have import section').to.be.true;\n});",
"lineNumber": 72
},
{
"pattern": "l'import externe ne déclenche pas d'alerte doublon",
"keyword": "Then",
"file": "form.steps.ts",
"sourceCode": "Then('l\\'import externe ne déclenche pas d\\'alerte doublon', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('create-event');\n const source = this.getRenderedText();\n // CreateEventScreen.tsx has: importedFrom state and !importedFrom in showDuplicateWarning condition\n expect(/importedFrom/.test(source), 'Form should track import source').to.be.true;\n expect(/&& !importedFrom/.test(source), 'Duplicate warning should be disabled for imports').to.be.true;\n});",
"lineNumber": 82
},
{
"pattern": "je peux voir la liste des participants",
"keyword": "Then",
@@ -325,12 +346,19 @@ export const stepDefinitions: StepDefinitionInfo[] = [
"sourceCode": "Then('je peux voir les événements auxquels l\\'utilisateur a participé', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx: \"Événements à venir\" and \"Événements passés\" sections\n expect(/Événements à venir/.test(source), 'User profile should have \"Événements à venir\" section').to.be.true;\n expect(/Événements passés/.test(source), 'User profile should have \"Événements passés\" section').to.be.true;\n});",
"lineNumber": 175
},
{
"pattern": "les événements affichent leur localisation et distance",
"keyword": "Then",
"file": "screen.steps.ts",
"sourceCode": "Then('les événements affichent leur localisation et distance', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('user-profile');\n const source = this.getRenderedText();\n // UserProfileScreen.tsx: events have location and distance in data\n expect(/location: '[^']+'/.test(source), 'Events should have location data').to.be.true;\n expect(/distance: \\d+/.test(source), 'Events should have distance data').to.be.true;\n // Verify location is rendered in template\n expect(/\\{event\\.location\\}/.test(source), 'Events should render location').to.be.true;\n expect(/\\{event\\.distance\\}/.test(source), 'Events should render distance').to.be.true;\n});",
"lineNumber": 183
},
{
"pattern": "je peux configurer mes notifications",
"keyword": "Then",
"file": "screen.steps.ts",
"sourceCode": "Then('je peux configurer mes notifications', async function (this: FestipodWorld) {\n expect(this.currentScreenId).to.equal('settings');\n const source = this.getRenderedText();\n // SettingsScreen.tsx line 25: <Text>Notifications</Text> with Toggle\n expect(/>Notifications</.test(source), 'Settings should have \"Notifications\" text').to.be.true;\n expect(/<Toggle[^>]*checked=\\{notifications\\}/.test(source), 'Settings should have Toggle for notifications').to.be.true;\n});",
"lineNumber": 183
"lineNumber": 194
}
];
+46 -30
View File
@@ -15,11 +15,11 @@ interface RawFeatureTestStatus {
const rawResults: RawFeatureTestStatus[] = [
{
"featureId": "us-13",
"totalScenarios": 7,
"passed": 4,
"totalScenarios": 10,
"passed": 7,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Accéder au formulaire de relai d'événement",
@@ -37,6 +37,18 @@ const rawResults: RawFeatureTestStatus[] = [
"name": "Pouvoir annuler le relai d'événement",
"status": "passed"
},
{
"name": "Détecter un événement similaire déjà relayé",
"status": "passed"
},
{
"name": "Importer un événement depuis une source externe",
"status": "passed"
},
{
"name": "Pas d'alerte doublon lors d'un import externe",
"status": "passed"
},
{
"name": "Modifier un événement",
"status": "skipped"
@@ -57,7 +69,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Accéder aux détails d'un événement terminé",
@@ -79,7 +91,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Voir les commentaires existants",
@@ -109,7 +121,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.878Z",
"scenarios": [
{
"name": "Consulter un événement avant inscription",
@@ -143,7 +155,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 8,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Consulter un macro-événement",
@@ -185,7 +197,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 4,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux points de rencontre",
@@ -211,7 +223,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.769Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Partager un événement auquel je participe",
@@ -241,7 +253,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 1,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Configurer les notifications de nouveaux participants",
@@ -267,7 +279,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les événements à venir sur l'accueil",
@@ -293,11 +305,11 @@ const rawResults: RawFeatureTestStatus[] = [
},
{
"featureId": "us-10",
"totalScenarios": 5,
"passed": 4,
"totalScenarios": 6,
"passed": 5,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au profil d'un participant",
@@ -307,6 +319,10 @@ const rawResults: RawFeatureTestStatus[] = [
"name": "Voir les événements du participant",
"status": "passed"
},
{
"name": "Voir la localisation des événements",
"status": "passed"
},
{
"name": "Voir le formulaire de contact",
"status": "passed"
@@ -327,7 +343,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la liste des événements depuis le profil",
@@ -365,7 +381,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la liste des inscrits d'un événement",
@@ -395,7 +411,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à mon profil",
@@ -429,7 +445,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de profil",
@@ -459,7 +475,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au partage de profil",
@@ -489,7 +505,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 5,
"failed": 0,
"skipped": 0,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au partage depuis le profil",
@@ -519,7 +535,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 2,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de notification",
@@ -541,7 +557,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 1,
"failed": 0,
"skipped": 2,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux paramètres de notification",
@@ -563,7 +579,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 3,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au formulaire de relai d'événement",
@@ -589,7 +605,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 4,
"failed": 0,
"skipped": 1,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au profil pour voir la photo",
@@ -619,7 +635,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder aux détails d'un événement terminé",
@@ -649,7 +665,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 3,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder au bilan consolidé",
@@ -671,7 +687,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 7,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la création d'atelier",
@@ -709,7 +725,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Accéder à la zone de notes personnelles",
@@ -739,7 +755,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 5,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les commentaires existants d'un atelier",
@@ -769,7 +785,7 @@ const rawResults: RawFeatureTestStatus[] = [
"passed": 0,
"failed": 0,
"skipped": 4,
"lastRun": "2026-01-26T17:08:25.770Z",
"lastRun": "2026-01-26T17:31:35.879Z",
"scenarios": [
{
"name": "Voir les ateliers d'un événement",
+108 -4
View File
@@ -2,14 +2,38 @@ import React, { useState } from 'react';
import { Header, Text, Input, Button, Placeholder } from '../components/sketchy';
import type { ScreenProps } from './index';
// Demo data for suggestions
const existingEvents = [
{ name: 'Résidence Reconnexion', relayedBy: 'Thomas Martin' },
];
const importableEvents = [
{
name: 'Festival des Utopies Concrètes',
source: 'Mobilizon',
date: '2026-03-15',
location: 'Paris, Parc de la Villette',
description: 'Festival annuel présentant des alternatives concrètes pour un monde durable.',
},
{
name: 'Rencontres de l\'Écologie',
source: 'Transiscope',
date: '2026-04-20',
location: 'Lyon, Halle Tony Garnier',
description: 'Deux jours de conférences et ateliers sur la transition écologique.',
},
];
export function CreateEventScreen({ navigate }: ScreenProps) {
const [name, setName] = useState('');
const [startDate, setStartDate] = useState('');
const [location, setLocation] = useState('');
const [description, setDescription] = useState('');
const [showSuggestions, setShowSuggestions] = useState(false);
const [importedFrom, setImportedFrom] = useState<string | null>(null);
// Show warning only when key fields are filled
const showDuplicateWarning = name.length > 3 && startDate && location.length > 3;
// Show warning only when key fields are filled AND not imported from external source
const showDuplicateWarning = name.length > 3 && startDate && location.length > 3 && !importedFrom;
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
@@ -53,13 +77,93 @@ export function CreateEventScreen({ navigate }: ScreenProps) {
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div>
<div style={{ position: 'relative' }}>
<Text style={{ marginBottom: 6, fontSize: 14 }}>Nom de l'événement *</Text>
<Input
placeholder="Donnez un nom à votre événement"
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
setShowSuggestions(e.target.value.length > 0);
setImportedFrom(null); // Reset import flag when user types manually
}}
onFocus={() => name.length > 0 && setShowSuggestions(true)}
onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
/>
{/* Suggestions dropdown */}
{showSuggestions && (
<div style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
background: 'white',
border: '2px solid var(--sketch-black)',
borderRadius: 8,
marginTop: 4,
zIndex: 10,
maxHeight: 250,
overflow: 'auto',
}}>
{/* Existing events - not selectable */}
{existingEvents.length > 0 && (
<>
<div style={{ padding: '8px 12px', background: 'var(--sketch-light-gray)', fontSize: 12, fontWeight: 'bold' }}>
Déjà relayé sur Festipod
</div>
{existingEvents.map((event, i) => (
<div
key={`existing-${i}`}
style={{
padding: '10px 12px',
borderBottom: '1px solid var(--sketch-light-gray)',
opacity: 0.6,
cursor: 'not-allowed',
}}
>
<Text style={{ margin: 0, fontSize: 14 }}>{event.name}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 12, color: 'var(--sketch-gray)' }}>
Relayé par {event.relayedBy}
</Text>
</div>
))}
</>
)}
{/* Importable events */}
{importableEvents.length > 0 && (
<>
<div style={{ padding: '8px 12px', background: 'var(--sketch-light-gray)', fontSize: 12, fontWeight: 'bold' }}>
Importer depuis une source externe
</div>
{importableEvents.map((event, i) => (
<div
key={`import-${i}`}
style={{
padding: '10px 12px',
borderBottom: '1px solid var(--sketch-light-gray)',
cursor: 'pointer',
}}
onClick={() => {
setName(event.name);
setStartDate(event.date);
setLocation(event.location);
setDescription(event.description);
setImportedFrom(event.source);
setShowSuggestions(false);
}}
>
<Text style={{ margin: 0, fontSize: 14 }}>{event.name}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 12, color: 'var(--sketch-gray)' }}>
via {event.source} · {event.location}
</Text>
</div>
))}
</>
)}
</div>
)}
</div>
<div style={{ display: 'flex', gap: 12 }}>
+1
View File
@@ -39,6 +39,7 @@ export function EventDetailScreen({ navigate }: ScreenProps) {
</Text>
<Text style={{ margin: 0, fontSize: 15 }}>
📍 <span className="user-content">Le Revel, Rogues (30)</span>
<span style={{ color: 'var(--sketch-gray)' }}> · 142 km</span>
</Text>
</div>
+8 -1
View File
@@ -2,10 +2,11 @@ import React from 'react';
import { Header, Input, Card, Text, Badge, NavBar } from '../components/sketchy';
import type { ScreenProps } from './index';
function EventCard({ title, date, location, attendees, onClick }: {
function EventCard({ title, date, location, distance, attendees, onClick }: {
title: string;
date: string;
location: string;
distance: number;
attendees: number;
onClick: () => void;
}) {
@@ -17,6 +18,7 @@ function EventCard({ title, date, location, attendees, onClick }: {
</Text>
<Text style={{ margin: '0 0 8px 0', fontSize: 14 }}>
📍 <span className="user-content">{location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {distance} km</span>
</Text>
<Badge>{attendees} inscrits</Badge>
</Card>
@@ -68,6 +70,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Résidence Reconnexion"
date="Lun. 16 - Ven. 20 fév."
location="Le Revel, Rogues (30)"
distance={142}
attendees={24}
onClick={() => navigate('event-detail')}
/>
@@ -75,6 +78,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Atelier low-tech"
date="Sam. 8 fév. · 14h00"
location="La Maison du Vélo, Lyon"
distance={3}
attendees={12}
onClick={() => navigate('event-detail')}
/>
@@ -82,6 +86,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Forum Ouvert Transition"
date="Sam. 22 fév. · 9h00"
location="Tiers-lieu L'Hermitage"
distance={89}
attendees={45}
onClick={() => navigate('event-detail')}
/>
@@ -89,6 +94,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Formation CNV"
date="Sam. 1 mars · 9h30"
location="MJC Montplaisir, Lyon"
distance={5}
attendees={16}
onClick={() => navigate('event-detail')}
/>
@@ -96,6 +102,7 @@ export function EventsScreen({ navigate }: ScreenProps) {
title="Rencontre des Colibris"
date="Mer. 12 fév. · 19h00"
location="La Maison de l'Environnement"
distance={7}
attendees={30}
onClick={() => navigate('event-detail')}
/>
+8 -2
View File
@@ -2,14 +2,17 @@ import React from 'react';
import { Button, Title, Text, Card, NavBar, Badge } from '../components/sketchy';
import type { ScreenProps } from './index';
function EventCard({ title, date, location, attendees, onClick }: { title: string; date: string; location: string; attendees: number; onClick: () => void }) {
function EventCard({ title, date, location, distance, attendees, onClick }: { title: string; date: string; location: string; distance: number; attendees: number; onClick: () => void }) {
return (
<Card onClick={onClick} style={{ marginBottom: 12 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<Text className="user-content" style={{ margin: 0, fontWeight: 'bold' }}>{title}</Text>
<Text className="user-content" style={{ margin: '4px 0 0 0', fontSize: 14 }}>{date}</Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>📍 <span className="user-content">{location}</span></Text>
<Text style={{ margin: '2px 0 0 0', fontSize: 14 }}>
📍 <span className="user-content">{location}</span>
<span style={{ color: 'var(--sketch-gray)' }}> · {distance} km</span>
</Text>
</div>
<Badge>{attendees} inscrits</Badge>
</div>
@@ -57,6 +60,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Résidence Reconnexion"
date="Lun. 16 - Ven. 20 fév."
location="Le Revel, Rogues (30)"
distance={142}
attendees={24}
onClick={() => navigate('event-detail')}
/>
@@ -64,6 +68,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Atelier low-tech"
date="Sam. 8 fév. · 14h00"
location="La Maison du Vélo, Lyon"
distance={3}
attendees={12}
onClick={() => navigate('event-detail')}
/>
@@ -71,6 +76,7 @@ export function HomeScreen({ navigate }: ScreenProps) {
title="Forum Ouvert Transition"
date="Sam. 22 fév. · 9h00"
location="Tiers-lieu L'Hermitage"
distance={89}
attendees={45}
onClick={() => navigate('event-detail')}
/>
+14 -6
View File
@@ -4,15 +4,15 @@ import type { ScreenProps } from './index';
export function UserProfileScreen({ navigate }: ScreenProps) {
const upcomingEvents = [
{ title: 'Résidence Reconnexion', date: '16-20 fév.', common: true },
{ title: 'Atelier permaculture', date: '28 fév.', common: false },
{ 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 pastEvents = [
{ title: 'Forum Ouvert Transition', date: '22 jan.', common: true },
{ title: 'Rencontre des Colibris', date: '12 jan.', common: true },
{ title: 'Formation CNV', date: '8 jan.', common: false },
{ title: 'Café des possibles', date: '15 déc.', common: false },
{ 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 (
@@ -65,6 +65,10 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
<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>
@@ -86,6 +90,10 @@ export function UserProfileScreen({ navigate }: ScreenProps) {
<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>