-
Notifications
You must be signed in to change notification settings - Fork 17
ECHO-615 ECHO-543 Onboarding cards update #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis PR refactors the participant onboarding system to remove Directus API calls for tutorial cards and store card definitions as JSON in the frontend. Tutorial slides are now generated based on a slug field ( Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
LGTM. Clean architectural move—decoupling from Directus CMS for onboarding and baking card logic into the frontend with slug-based routing. The slug normalization and multi-language fallback to en-US are solid patterns. Just watch the control flow paths in useOnboardingCards; four branches can hide edge cases. Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
echo/frontend/src/components/participant/ParticipantOnboardingCards.tsx (1)
78-204: Cards object definition is solid but rebuilt on every render.The inline
cardsobject with spreadgetSystemCards()works. If perf becomes a concern, wrap this inuseMemowith[language, tutorialSlug, MicrophoneTestComponent, InitiateFormComponent]deps. For now, LGTM.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
echo/directus/sync/collections/operations.json(12 hunks)echo/directus/sync/snapshot/fields/project/default_conversation_tutorial_slug.json(1 hunks)echo/frontend/src/components/participant/ParticipantOnboardingCards.tsx(6 hunks)echo/frontend/src/components/participant/hooks/useOnboardingCards.ts(1 hunks)echo/frontend/src/components/project/ProjectPortalEditor.tsx(3 hunks)echo/frontend/src/locales/de-DE.po(42 hunks)echo/frontend/src/locales/en-US.po(43 hunks)echo/frontend/src/locales/es-ES.po(42 hunks)echo/frontend/src/locales/fr-FR.po(42 hunks)echo/frontend/src/locales/nl-NL.po(43 hunks)echo/frontend/src/routes/participant/ParticipantStart.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
echo/frontend/**/src/routes/**/*.tsx
📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)
Use Lingui macros
tfrom@lingui/core/macroandTransfrom@lingui/react/macrofor localizing UI strings in routed screens
Files:
echo/frontend/src/routes/participant/ParticipantStart.tsx
echo/frontend/**/src/{routes,components}/**/*.tsx
📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)
echo/frontend/**/src/{routes,components}/**/*.tsx: Compose Mantine primitives (Stack,Group,ActionIcon, etc.) while layering Tailwind utility classes viaclassName, alongside toast feedback via@/components/common/Toaster
For Tailwind classes that need dynamic values, replace with inlinestyleprops using CSS variables instead of hardcoded hex values
Files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/components/participant/ParticipantOnboardingCards.tsxecho/frontend/src/components/project/ProjectPortalEditor.tsx
echo/frontend/**/src/**/*.{css,tsx,ts}
📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)
Use CSS variables
var(--app-background)andvar(--app-text)instead of hardcoded colors like#F6F4F1or#2D2D2Cto ensure theme changes propagate
Files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/components/participant/ParticipantOnboardingCards.tsxecho/frontend/src/components/project/ProjectPortalEditor.tsxecho/frontend/src/components/participant/hooks/useOnboardingCards.ts
echo/frontend/**/src/{theme.tsx,components,routes}/**/*.tsx
📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)
When adding new Mantine components, reference the global Mantine theme in
src/theme.tsxwith customprimarycolor palette and component defaults, preferring CSS variables in styles over hardcoded hex values
Files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/components/participant/ParticipantOnboardingCards.tsxecho/frontend/src/components/project/ProjectPortalEditor.tsx
echo/frontend/**/src/{components,routes}/**/*.tsx
📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)
UI mutations should surface inline feedback: pair toasts with contextual Mantine
Alertcomponents inside modals/forms for errors or warnings
Files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/components/participant/ParticipantOnboardingCards.tsxecho/frontend/src/components/project/ProjectPortalEditor.tsx
🧠 Learnings (12)
📓 Common learnings
Learnt from: ussaama
Repo: Dembrane/echo PR: 205
File: echo/frontend/src/lib/query.ts:1444-1506
Timestamp: 2025-07-10T12:48:20.683Z
Learning: ussaama prefers string concatenation over template literals for simple cases where readability is clearer, even when linting tools suggest template literals. Human readability takes precedence over strict linting rules in straightforward concatenation scenarios.
📚 Learning: 2025-08-08T10:39:31.114Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 259
File: echo/frontend/src/components/layout/ParticipantLayout.tsx:33-33
Timestamp: 2025-08-08T10:39:31.114Z
Learning: In echo/frontend/src/components/layout/ParticipantLayout.tsx, prefer using simple pathname.includes("start") and pathname.includes("finish") to control the settings button visibility. No need to switch to segment-based matching or add a useEffect to auto-close the modal for these routes, per ussaama’s preference in PR #259.
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/components/participant/ParticipantOnboardingCards.tsxecho/frontend/src/components/participant/hooks/useOnboardingCards.tsecho/frontend/src/locales/en-US.po
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/**/*.tsx : Use Lingui macros `t` from `lingui/core/macro` and `Trans` from `lingui/react/macro` for localizing UI strings in routed screens
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsxecho/frontend/src/locales/de-DE.poecho/frontend/src/locales/es-ES.poecho/frontend/src/locales/nl-NL.poecho/frontend/src/locales/fr-FR.poecho/frontend/src/locales/en-US.po
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Gentle login/logout flows use `useTransitionCurtain().runTransition()` before navigation—animations expect Directus session mutations to await that promise
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsx
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Auth hero uses `/public/video/auth-hero.mp4` with `/public/video/auth-hero-poster.jpg` as poster; keep the bright blur overlay consistent when iterating on onboarding screens
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsx
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/settings/**/*.tsx : Provide ergonomic navigation in settings-like routes: breadcrumb + back action (ActionIcon + navigate(-1)) with relevant iconography is the default
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsx
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Auth surfaces reuse `HeaderView` by passing `isAuthenticated`/`loading` props—avoid rolling bespoke headers inside layouts
Applied to files:
echo/frontend/src/routes/participant/ParticipantStart.tsx
📚 Learning: 2025-07-10T12:47:06.269Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 205
File: echo/frontend/src/locales/nl-NL.po:1246-1247
Timestamp: 2025-07-10T12:47:06.269Z
Learning: .po files (gettext internationalization files) are auto-generated by i18n tools like lingui/cli and should not be manually edited. The format of component paths and line numbers in these files is determined by the tool configuration, not manually written. Do not suggest manual edits to .po files.
Applied to files:
echo/frontend/src/locales/de-DE.poecho/frontend/src/locales/es-ES.poecho/frontend/src/locales/en-US.po
📚 Learning: 2025-05-30T15:38:44.413Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 169
File: echo/frontend/src/components/project/ProjectPortalEditor.tsx:409-464
Timestamp: 2025-05-30T15:38:44.413Z
Learning: Badge-based selectors in ProjectPortalEditor.tsx: Keyboard navigation enhancements for accessibility are considered optional improvements rather than critical issues. The user acknowledges these suggestions but doesn't prioritize them as blockers.
Applied to files:
echo/frontend/src/locales/de-DE.poecho/frontend/src/locales/es-ES.poecho/frontend/src/locales/nl-NL.poecho/frontend/src/locales/fr-FR.poecho/frontend/src/locales/en-US.po
📚 Learning: 2025-10-28T13:47:02.926Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 350
File: echo/frontend/src/components/participant/ParticipantConversationText.tsx:82-85
Timestamp: 2025-10-28T13:47:02.926Z
Learning: In text mode (echo/frontend/src/components/participant/ParticipantConversationText.tsx), participants only submit PORTAL_TEXT chunks (no audio). The “Finish” button is shown only after at least one text message is saved to Directus.
Applied to files:
echo/frontend/src/locales/de-DE.poecho/frontend/src/locales/nl-NL.poecho/frontend/src/locales/fr-FR.poecho/frontend/src/locales/en-US.po
📚 Learning: 2025-12-05T00:33:33.442Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/lib/{directus,api}.ts : Standardize Directus error handling and add localization polish in `src/lib/directus.ts`; custom Directus POSTs should call `directus.request` with a function signature rather than `restRequest`
Applied to files:
echo/directus/sync/collections/operations.json
📚 Learning: 2025-09-16T08:34:38.109Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 293
File: echo/frontend/src/components/chat/ChatTemplatesMenu.tsx:15-16
Timestamp: 2025-09-16T08:34:38.109Z
Learning: In ChatTemplatesMenu.tsx, titles are preferred over IDs for template selection logic since titles are unique one-liners and work effectively as identifiers. IDs were added primarily for better list rendering rather than selection purposes.
Applied to files:
echo/frontend/src/locales/en-US.po
🧬 Code graph analysis (1)
echo/frontend/src/components/participant/hooks/useOnboardingCards.ts (1)
echo/frontend/src/components/participant/ParticipantOnboardingCards.tsx (1)
LanguageCards(41-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-check-server
🔇 Additional comments (20)
echo/frontend/src/locales/es-ES.po (1)
1-4036: LGTM! Auto-generated i18n updates ship clean.This
.pofile is auto-generated by@lingui/cliand reflects the translation updates for the onboarding cards refactor. The new tutorial options (skip-consent,none,basic,advanced) are properly translated, obsolete entries are correctly marked with#~, and the coordinate withProjectPortalEditor.tsxchanges tracks perfectly.Based on learnings, manual edits to
.pofiles are not the way—these get regenerated on the next extraction run. Translation quality improvements should flow through the source strings, not patched here.echo/frontend/src/locales/fr-FR.po (2)
229-236: LGTM, localization strings are on point.New tutorial slug options (
skip-consent,none,basic,advanced) properly mapped to French translations. The obsolete entry preservation with#~markers is solid for history tracking. Ship it.
884-887: New tutorial option translations look clean."Par défaut - Pas de tutoriel" and "Passer la carte de confidentialité" are correctly localized for the new slug-based flow. Consistent with the ProjectPortalEditor changes.
Also applies to: 3070-3072
echo/frontend/src/components/participant/ParticipantOnboardingCards.tsx (3)
45-47: Props simplified nicely, typing is clean.Removing the
initialCardsprop in favor of deriving fromprojectis the right call. Single source of truth = less surface area for bugs.
76-77: Default fallback to "none" is consistent.This matches the normalization logic in ProjectPortalEditor. Consider extracting
"none"as a constant if this default appears in more places.
206-210: Fallback to empty array is defensive coding, nice.If
languagedoesn't match any key, you get[]→allSlides = []→currentCard = undefined→ fallback UI at line 221. Solid error boundary pattern.echo/frontend/src/routes/participant/ParticipantStart.tsx (1)
26-30: Loading state simplified, clean.Single dependency on
isLoadingProjectinstead of multiple loading flags. Reduced complexity = reduced bugs. 10x approved.echo/frontend/src/components/project/ProjectPortalEditor.tsx (2)
230-236: Slug normalization is solid defensive coding.Lowercase conversion + allowlist validation + fallback to "none" handles legacy data and edge cases cleanly. This is the right pattern for migrating schema changes.
529-545: New tutorial options aligned with backend schema.
skip-consent,none,basic,advanced- the full slug matrix. Labels use Linguitmacro correctly. As per coding guidelines, this is using proper localization patterns.echo/directus/sync/collections/operations.json (1)
17-21: No action needed — all UUID references are internally consistent.All
resolveandrejectUUIDs in the file correctly reference valid_syncIdvalues within the same operations.json. The ID re-mapping is normal Directus sync behavior, and there are no dangling flow references or deployment risks.Likely an incorrect or invalid review comment.
echo/frontend/src/locales/nl-NL.po (2)
536-545: Onboarding button labels + “Ready” copy look tight and consistent.The new participant onboarding button strings (
Terug naar microfoon,Terug,Doorgaan,Volgende,Klaar om te beginnen) read naturally in Dutch and line up with the updated card flow. No changes needed here.Also applies to: 763-768, 2049-2052, 2706-2709
244-247: Portal editor + tutorial option labels are aligned with the new slug-based onboarding.The updated labels for tutorial intensity and portal content (
"Advanced (Tips and best practices)","Basis (Alleen essentiële tutorial slides)","Standaard - Geen tutorial (Alleen privacy verklaringen)","Concrete onderwerpen", page content/title/help texts, and language/mode labels) all read well and match the intended behavior from the PR description. This should keep the Dutch portal UX in lockstep with the new onboarding configuration.Also applies to: 553-556, 940-947, 651-652, 710-713, 975-976, 1005-1008, 1790-1793, 1865-1868, 1965-1968, 2001-2004, 2303-2306, 2311-2314
echo/frontend/src/locales/de-DE.po (3)
109-115: Portal editor + onboarding config strings line up with new card behaviorThe new PortalEditor strings for tutorial selection, language/mode, concrete topics, reply prompt, portal/thank-you pages, report notifications, and the live preview all hang together with the new onboarding-card logic. Placeholders and grammar look sane, and the generic
{0}entry is reused correctly for the new editor context. Nothing blocking from a product/i18n perspective—this will scale fine with the JSON-based onboarding config.Based on learnings, I’m assuming these were generated via the Lingui flow and not edited by hand.
Also applies to: 229-236, 496-503, 651-656, 703-707, 745-753, 771-779, 877-884, 1003-1006, 1088-1121, 1134-1137, 1459-1463, 1670-1673, 1745-1748, 1859-1862, 2168-2188, 2430-2437, 3435-3446, 3450-3453
478-482: Participant onboarding card buttons look consistent with the new flowThe new
ParticipantOnboardingCardskeys for back (including mic-check–specific back), continue, next, “ready to begin,” and the mic-check skip all resolve to short, consistent German labels (Zurück,Weiter,Überspringen, etc.). That matches the revamped onboarding-card flow (skip-consent, basic, advanced) without introducing weird phrasing. Looks tight.Based on learnings, treating these as the Lingui output, not something to tweak directly.
Also applies to: 484-488, 707-711, 1929-1932, 2553-2556, 3056-3059
280-283: At least one untranslated i18n key remains in this fileThe key
participant.modal.refine.info.available.in(line 3411) still has an English msgstr placeholder. Additionally,dashboard.dembrane.concrete.description(line 1102) has msgstr equal to msgid, indicating no translation. While some strings in the originally cited ranges have since been properly translated, these two keys should be added to the localization backlog to ensure the German UI displays correctly when these features are fully exposed.⛔ Skipped due to learnings
Learnt from: ussaama Repo: Dembrane/echo PR: 180 File: echo/frontend/src/locales/es-ES.ts:1-1 Timestamp: 2025-06-06T05:43:44.935Z Learning: When users indicate they have already addressed translation issues in locale files, verify the current state before assuming the issue persists. Translation files are often updated in bulk and previously flagged English text may have been properly localized.echo/frontend/src/locales/en-US.po (1)
478-480: LGTM! Locale strings align with the new slug-based onboarding flow.The new translation entries for tutorial options match the slug values used in the code. Auto-generated tooling handled the reordering and updates correctly.
Also applies to: 531-543, 1164-1167
echo/frontend/src/components/participant/hooks/useOnboardingCards.ts (4)
1-12: LGTM! Icon imports are on point.All the new lucide-react icons are used in the tutorial slides. Clean import organization.
404-405: Language fallback implementation is clean.Proper fallback chain: requested language → en-US → empty array. Handles missing translations gracefully.
Also applies to: 923-924
927-1044: Privacy card logic LGTM.The getPrivacyCard function is solid and correctly handles all supported languages with proper null returns for unsupported locales.
16-51: [Your rewritten review comment text here]
[Exactly ONE classification tag]
| "note": null, | ||
| "options": { | ||
| "choices": [ | ||
| { | ||
| "text": "Skip Consent", | ||
| "value": "skip-consent" | ||
| }, | ||
| { | ||
| "text": "None", | ||
| "value": "None" | ||
| "value": "none" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat echo/directus/sync/snapshot/fields/project/default_conversation_tutorial_slug.jsonRepository: Dembrane/echo
Length of output: 1498
🏁 Script executed:
rg -i "default_conversation_tutorial_slug" --type ts --type tsx --type js --type jsx -A 5 -B 2Repository: Dembrane/echo
Length of output: 84
🏁 Script executed:
rg "default_conversation_tutorial_slug" -A 5 -B 2Repository: Dembrane/echo
Length of output: 14171
🏁 Script executed:
rg "None.*basic.*advanced" --type ts -B 5 -A 5Repository: Dembrane/echo
Length of output: 1087
🏁 Script executed:
fd "migration" -t f | head -20Repository: Dembrane/echo
Length of output: 89
🏁 Script executed:
cat echo/docs/database_migrations.mdRepository: Dembrane/echo
Length of output: 632
🏁 Script executed:
head -20 echo/frontend/src/lib/typesDirectus.d.tsRepository: Dembrane/echo
Length of output: 840
🏁 Script executed:
git log --oneline echo/directus/sync/snapshot/fields/project/default_conversation_tutorial_slug.json | head -10Repository: Dembrane/echo
Length of output: 95
🏁 Script executed:
git log -p --follow -1 echo/directus/sync/snapshot/fields/project/default_conversation_tutorial_slug.json | head -100Repository: Dembrane/echo
Length of output: 39
🏁 Script executed:
grep -A 2 -B 2 '"None"' echo/frontend/src/routes/project/ProjectsHome.tsxRepository: Dembrane/echo
Length of output: 261
Update TypeScript types and new project defaults for consistency; legacy data is safely handled by frontend.
The lowercase choice values ("skip-consent", "none", "basic", "advanced") and default_value: "none" align well with the frontend's slug normalization logic. However, a few inconsistencies need cleanup:
ProjectsHome.tsxstill creates new projects with"None"(uppercase), which will be normalized by the frontend'stoLowerCase()validation fallback, but should use"none"directly.typesDirectus.d.tsdefines the type as"None" | "basic" | "advanced"(missing"skip-consent"and using uppercase"None"); update it to match the schema choices.- Existing rows with
"None"(uppercase) will appear as unlisted choices in Directus UI, though the frontend safely defaults them to"none". If you want a clean admin experience, backfill"None"→"none"in the database.
🤖 Prompt for AI Agents
In
echo/directus/sync/snapshot/fields/project/default_conversation_tutorial_slug.json
around lines 14-24, the schema choices are lowercase but the code/types still
reference an uppercase "None" and are missing "skip-consent"; update
ProjectsHome.tsx to create new projects with default_value "none" (lowercase),
update typesDirectus.d.ts to include "none" | "skip-consent" | "basic" |
"advanced" (all lowercase) so TypeScript matches the schema, and optionally run
a database backfill to convert existing "None" values to "none" for a consistent
Directus admin UI.
| const privacyCard = getPrivacyCard(lang); | ||
| return [...tutorialCards, ...(privacyCard ? [privacyCard] : [])]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Privacy card integration is inconsistent between basic and advanced.
In basic (line 41), privacy card is appended as a separate section:
return [...tutorialCards, ...(privacyCard ? [privacyCard] : [])];In advanced (lines 477, 579, etc), privacy slides are spread into the existing Privacy section:
...(getPrivacyCard("de-DE")?.slides || [])This works but creates inconsistency. Consider unifying the approach or document why they differ.
Also applies to: 477-477, 579-579, 680-680, 783-783, 885-885
| const getBasicTutorialCards = (lang: string): LanguageCards[string] => { | ||
| const tutorialCards: Record<string, LanguageCards[string]> = { | ||
| "de-DE": [ | ||
| { | ||
| section: "Willkommen", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Nehmen Sie Ihre Stimme auf, um Fragen zu beantworten und Einfluss zu nehmen.", | ||
| cta: "Los geht's!", | ||
| extraHelp: | ||
| "Dies ist ein Mini-Tutorial. Nutzen Sie die Vor- und Zurück-Tasten zur Navigation. Nach Abschluss gelangen Sie zum Aufnahmeportal.", | ||
| icon: PartyPopper, | ||
| title: "Willkommen bei Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane hilft Menschen, einfach Input von großen Gruppen zu sammeln.", | ||
| cta: "Mehr erfahren", | ||
| extraHelp: | ||
| "Ob Feedback für eine Kommune, Input im Arbeitsumfeld oder Teilnahme an Forschung – Ihre Stimme zählt!", | ||
| icon: Orbit, | ||
| title: "Was ist Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Beantworten Sie Fragen in Ihrem eigenen Tempo durch Sprechen oder Tippen.", | ||
| cta: "Weiter", | ||
| extraHelp: | ||
| "Spracheingabe ist unser bevorzugter Modus, da sie natürlichere und detailliertere Antworten ermöglicht. Tippen steht immer als Alternative zur Verfügung.", | ||
| icon: Speech, | ||
| title: "Sagen Sie einfach Ihre Meinung", | ||
| }, | ||
| { | ||
| content: "Dembrane macht in Gruppen mehr Spaß!", | ||
| cta: "Weiter", | ||
| extraHelp: | ||
| "Dembrane macht mehr Spaß, wenn Sie jemanden finden, um die Fragen gemeinsam zu besprechen und Ihr Gespräch aufzunehmen. Wir können nicht sagen, wer was gesagt hat, nur welche Ideen geteilt wurden.", | ||
| icon: MessagesSquare, | ||
| title: "Allein oder in der Gruppe", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Wie es funktioniert", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Sie erhalten die Fragen, sobald Sie im Aufnahmeportal sind.", | ||
| cta: "Verstanden", | ||
| extraHelp: | ||
| "Die Fragen variieren je nach den Bedürfnissen des Gastgebers. Sie können sich auf kommunale Themen, Arbeitserfahrungen oder Forschungsthemen beziehen. Wenn es keine spezifischen Fragen gibt, können Sie gerne alle Gedanken oder Anliegen teilen.", | ||
| icon: HelpCircle, | ||
| title: "Fragerunde", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Datenschutz", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Als Aufnehmer haben Sie die Kontrolle über das, was Sie teilen.", | ||
| cta: "Mehr erfahren", | ||
| extraHelp: | ||
| "Vermeiden Sie die Weitergabe von Details, die Sie dem Gastgeber nicht mitteilen möchten. Seien Sie achtsam und nehmen Sie andere nicht ohne deren Zustimmung auf.", | ||
| icon: Lock, | ||
| title: "Datenschutz ist wichtig", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "en-US": [ | ||
| { | ||
| section: "Welcome", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Record your voice to answer questions and make an impact.", | ||
| cta: "Let's go!", | ||
| extraHelp: | ||
| "This is a mini-tutorial. Use the previous and next buttons to navigate. Once completed, you'll enter the recording portal.", | ||
| icon: PartyPopper, | ||
| title: "Welcome to Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane helps people gather input from large groups easily.", | ||
| cta: "Tell me more", | ||
| extraHelp: | ||
| "Whether it's feedback for a local municipality, input in a work setting, or participation in research, your voice matters!", | ||
| icon: Orbit, | ||
| title: "What is Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Answer questions in your own time by speaking or typing.", | ||
| cta: "Next", | ||
| extraHelp: | ||
| "Voice input is our primary mode, allowing for more natural and detailed responses. Typing is always available as a backup.", | ||
| icon: Speech, | ||
| title: "Just Speak Your Mind", | ||
| }, | ||
| { | ||
| content: "Dembrane is more fun in groups!", | ||
| cta: "Next", | ||
| extraHelp: | ||
| "Dembrane is more fun when you find someone to discuss the questions together and record your conversation. We can't tell who said what, just what ideas were shared.", | ||
| icon: MessagesSquare, | ||
| title: "Solo or in a Group", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "How it works", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "You'll receive the questions once in the recording portal.", | ||
| cta: "Got it", | ||
| extraHelp: | ||
| "Questions vary based on the host's needs. They could be about community issues, work experiences, or research topics. If there are no specific questions, you're free to share any thoughts or concerns.", | ||
| icon: HelpCircle, | ||
| title: "Question Time", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacy", | ||
| slides: [ | ||
| { | ||
| content: "As the recorder, you are in control of what you share.", | ||
| cta: "Tell me more", | ||
| extraHelp: | ||
| "Avoid sharing details you don't want the host to know. Be mindful and don't record others without their consent.", | ||
| icon: Lock, | ||
| title: "Privacy Matters", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "es-ES": [ | ||
| { | ||
| section: "Bienvenido", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Graba tu voz para responder preguntas y generar impacto.", | ||
| cta: "¡Vamos!", | ||
| extraHelp: | ||
| "Este es un mini-tutorial. Usa los botones de anterior y siguiente para navegar. Una vez completado, entrarás al portal de grabación.", | ||
| icon: PartyPopper, | ||
| title: "¡Bienvenido a Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane ayuda a las personas a recopilar aportaciones de grandes grupos fácilmente.", | ||
| cta: "Cuéntame más", | ||
| extraHelp: | ||
| "Ya sea retroalimentación para una municipalidad local, aportaciones en el trabajo o participación en investigación, ¡tu voz importa!", | ||
| icon: Orbit, | ||
| title: "¿Qué es Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Responde preguntas a tu propio ritmo hablando o escribiendo.", | ||
| cta: "Siguiente", | ||
| extraHelp: | ||
| "La entrada de voz es nuestro modo principal, permitiendo respuestas más naturales y detalladas. Escribir siempre está disponible como respaldo.", | ||
| icon: Speech, | ||
| title: "Solo Di Lo Que Piensas", | ||
| }, | ||
| { | ||
| content: "¡Dembrane es más divertido en grupos!", | ||
| cta: "Siguiente", | ||
| extraHelp: | ||
| "Dembrane es más divertido cuando encuentras a alguien para discutir las preguntas juntos y grabar su conversación. No podemos distinguir quién dijo qué, solo qué ideas se compartieron.", | ||
| icon: MessagesSquare, | ||
| title: "Solo o en Grupo", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Cómo funciona", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Recibirás las preguntas una vez en el portal de grabación.", | ||
| cta: "Entendido", | ||
| extraHelp: | ||
| "Las preguntas varían según las necesidades del anfitrión. Pueden ser sobre temas comunitarios, experiencias laborales o temas de investigación. Si no hay preguntas específicas, eres libre de compartir cualquier pensamiento o inquietud.", | ||
| icon: HelpCircle, | ||
| title: "Hora de Preguntas", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacidad", | ||
| slides: [ | ||
| { | ||
| content: "Como grabador, tú controlas lo que compartes.", | ||
| cta: "Cuéntame más", | ||
| extraHelp: | ||
| "Evita compartir detalles que no quieras que el anfitrión conozca. Sé consciente y no grabes a otros sin su consentimiento.", | ||
| icon: Lock, | ||
| title: "La Privacidad Importa", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "fr-FR": [ | ||
| { | ||
| section: "Bienvenue", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Enregistrez votre voix pour répondre aux questions et avoir un impact.", | ||
| cta: "C'est parti !", | ||
| extraHelp: | ||
| "Ceci est un mini-tutoriel. Utilisez les boutons précédent et suivant pour naviguer. Une fois terminé, vous entrerez dans le portail d'enregistrement.", | ||
| icon: PartyPopper, | ||
| title: "Bienvenue sur Dembrane !", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane aide les gens à recueillir facilement les contributions de grands groupes.", | ||
| cta: "Dites-m'en plus", | ||
| extraHelp: | ||
| "Qu'il s'agisse de commentaires pour une municipalité locale, de contributions dans un cadre professionnel ou de participation à une recherche, votre voix compte !", | ||
| icon: Orbit, | ||
| title: "Qu'est-ce que Dembrane ?", | ||
| }, | ||
| { | ||
| content: | ||
| "Répondez aux questions à votre rythme en parlant ou en tapant.", | ||
| cta: "Suivant", | ||
| extraHelp: | ||
| "La saisie vocale est notre mode principal, permettant des réponses plus naturelles et détaillées. La saisie au clavier est toujours disponible en secours.", | ||
| icon: Speech, | ||
| title: "Dites Simplement Ce Que Vous Pensez", | ||
| }, | ||
| { | ||
| content: "Dembrane est plus amusant en groupe !", | ||
| cta: "Suivant", | ||
| extraHelp: | ||
| "Dembrane est plus amusant lorsque vous trouvez quelqu'un pour discuter des questions ensemble et enregistrer votre conversation. Nous ne pouvons pas dire qui a dit quoi, juste quelles idées ont été partagées.", | ||
| icon: MessagesSquare, | ||
| title: "Seul ou en Groupe", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Comment ça marche", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Vous recevrez les questions une fois dans le portail d'enregistrement.", | ||
| cta: "Compris", | ||
| extraHelp: | ||
| "Les questions varient en fonction des besoins de l'hôte. Elles peuvent concerner des problèmes communautaires, des expériences professionnelles ou des sujets de recherche. S'il n'y a pas de questions spécifiques, vous êtes libre de partager vos pensées ou préoccupations.", | ||
| icon: HelpCircle, | ||
| title: "Heure des Questions", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Confidentialité", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "En tant qu'enregistreur, vous contrôlez ce que vous partagez.", | ||
| cta: "Dites-m'en plus", | ||
| extraHelp: | ||
| "Évitez de partager des détails que vous ne voulez pas que l'hôte connaisse. Soyez attentif et n'enregistrez pas les autres sans leur consentement.", | ||
| icon: Lock, | ||
| title: "La Confidentialité Compte", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "nl-NL": [ | ||
| { | ||
| section: "Welkom", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Neem je stem op om vragen te beantwoorden en impact te maken.", | ||
| cta: "Aan de slag!", | ||
| extraHelp: | ||
| "Dit is een mini-handleiding. Gebruik de knoppen om te navigeren. Na afloop van de handleiding kom je in de opnameportaal terecht.", | ||
| icon: PartyPopper, | ||
| title: "Welkom bij Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane helpt mensen gemakkelijk input van grote groepen te verzamelen.", | ||
| cta: "Vertel me meer", | ||
| extraHelp: | ||
| "Of het nu gaat om feedback voor de gemeente, input op het werk, of deelname aan onderzoek, jouw stem telt!", | ||
| icon: Orbit, | ||
| title: "Wat is Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Beantwoord vragen in je eigen tempo door te spreken of te typen.", | ||
| cta: "Volgende", | ||
| extraHelp: | ||
| "Spraak is onze voorkeursmethode, omdat het natuurlijker en gedetailleerder is. Typen kan natuurlijk ook altijd.", | ||
| icon: Speech, | ||
| title: "Zeg het maar", | ||
| }, | ||
| { | ||
| content: "Dembrane is leuker met een groep!", | ||
| cta: "Volgende", | ||
| extraHelp: | ||
| "Dembrane is leuker als je iemand vindt om de vragen samen te bespreken en jullie gesprek op te nemen. We kunnen niet horen wie wat zei, alleen welke ideeën er gedeeld zijn.", | ||
| icon: MessagesSquare, | ||
| title: "Alleen of in een groep", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Hoe het werkt", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Je krijgt de vragen te zien zodra je in de opnameportal bent.", | ||
| cta: "Begrepen", | ||
| extraHelp: | ||
| "Vragen variëren afhankelijk van wat de organisator wil weten. Het kan gaan over de buurt, werkervaringen, of onderzoeksonderwerpen. Als er geen specifieke vragen zijn, kun je gewoon je gedachten of zorgen delen.", | ||
| icon: HelpCircle, | ||
| title: "Vragenronde", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacy", | ||
| slides: [ | ||
| { | ||
| content: "Als opnemer heb je zelf controle over wat je deelt.", | ||
| cta: "Vertel me meer", | ||
| extraHelp: | ||
| "Vermijd het delen van details die je niet met de organisator wilt delen. Wees voorzichtig en neem anderen niet op zonder hun toestemming.", | ||
| icon: Lock, | ||
| title: "Privacy is belangrijk", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| // Fallback to English if language not found | ||
| return tutorialCards[lang] || tutorialCards["en-US"] || []; | ||
| }; | ||
|
|
||
| const getAdvancedTutorialCards = (lang: string): LanguageCards[string] => { | ||
| const tutorialCards: Record<string, LanguageCards[string]> = { | ||
| "de-DE": [ | ||
| { | ||
| section: "Willkommen", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Nehmen Sie Ihre Stimme auf, um Fragen zu beantworten und Einfluss zu nehmen.", | ||
| cta: "Los geht's!", | ||
| extraHelp: | ||
| "Dies ist ein Mini-Tutorial. Nutzen Sie die Vor- und Zurück-Tasten zur Navigation. Nach Abschluss gelangen Sie zum Aufnahmeportal.", | ||
| icon: PartyPopper, | ||
| title: "Willkommen bei Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane hilft Menschen, einfach Input von großen Gruppen zu sammeln.", | ||
| cta: "Mehr erfahren", | ||
| extraHelp: | ||
| "Ob Feedback für eine Kommune, Input im Arbeitsumfeld oder Teilnahme an Forschung – Ihre Stimme zählt!", | ||
| icon: Orbit, | ||
| title: "Was ist Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Beantworten Sie Fragen in Ihrem eigenen Tempo durch Sprechen oder Tippen.", | ||
| cta: "Weiter", | ||
| extraHelp: | ||
| "Spracheingabe ist unser bevorzugter Modus, da sie natürlichere und detailliertere Antworten ermöglicht. Tippen steht immer als Alternative zur Verfügung.", | ||
| icon: Speech, | ||
| title: "Sagen Sie einfach Ihre Meinung", | ||
| }, | ||
| { | ||
| content: "Dembrane macht in Gruppen mehr Spaß!", | ||
| cta: "Weiter", | ||
| extraHelp: | ||
| "Dembrane macht mehr Spaß, wenn Sie jemanden finden, um die Fragen gemeinsam zu besprechen und Ihr Gespräch aufzunehmen. Wir können nicht sagen, wer was gesagt hat, nur welche Ideen geteilt wurden.", | ||
| icon: MessagesSquare, | ||
| title: "Allein oder in der Gruppe", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Wie es funktioniert", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Sie erhalten die Fragen, sobald Sie im Aufnahmeportal sind.", | ||
| cta: "Verstanden", | ||
| extraHelp: | ||
| "Die Fragen variieren je nach den Bedürfnissen des Gastgebers. Sie können sich auf kommunale Themen, Arbeitserfahrungen oder Forschungsthemen beziehen. Wenn es keine spezifischen Fragen gibt, können Sie gerne alle Gedanken oder Anliegen teilen.", | ||
| icon: HelpCircle, | ||
| title: "Fragerunde", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Datenschutz", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Als Aufnehmer haben Sie die Kontrolle über das, was Sie teilen.", | ||
| cta: "Mehr erfahren", | ||
| extraHelp: | ||
| "Vermeiden Sie die Weitergabe von Details, die Sie dem Gastgeber nicht mitteilen möchten. Seien Sie achtsam und nehmen Sie andere nicht ohne deren Zustimmung auf.", | ||
| icon: Lock, | ||
| title: "Datenschutz ist wichtig", | ||
| }, | ||
| ...(getPrivacyCard("de-DE")?.slides || []), | ||
| ], | ||
| }, | ||
| { | ||
| section: "Best Practices", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Stellen Sie sich vor, Dembrane ist auf Lautsprecher mit Ihnen. Wenn Sie sich selbst hören können, ist alles in Ordnung.", | ||
| cta: "Verstanden", | ||
| extraHelp: | ||
| "Etwas Hintergrundgeräusch ist in Ordnung, solange klar ist, wer spricht.", | ||
| icon: Volume2, | ||
| title: "Hintergrundgeräusche reduzieren", | ||
| }, | ||
| { | ||
| content: | ||
| "Stellen Sie eine stabile Verbindung für eine reibungslose Aufnahme sicher.", | ||
| cta: "Bereit!", | ||
| extraHelp: | ||
| "WLAN oder gute mobile Daten funktionieren am besten. Wenn Ihre Verbindung abbricht, keine Sorge. Sie können immer dort weitermachen, wo Sie aufgehört haben.", | ||
| icon: Wifi, | ||
| title: "Starke Internetverbindung", | ||
| }, | ||
| { | ||
| content: | ||
| "Vermeiden Sie Unterbrechungen, indem Sie Ihr Gerät entsperrt halten. Wenn es sich sperrt, entsperren Sie es einfach und fahren Sie fort.", | ||
| cta: "Okay", | ||
| extraHelp: | ||
| "Dembrane versucht, Ihr Gerät aktiv zu halten, aber manchmal können Geräte dies überschreiben. Sie können Ihre Geräteeinstellungen anpassen, um länger entsperrt zu bleiben, wenn nötig.", | ||
| icon: Smartphone, | ||
| title: "Gerät nicht sperren!", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "en-US": [ | ||
| { | ||
| section: "Welcome", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Record your voice to answer questions and make an impact.", | ||
| cta: "Let's go!", | ||
| extraHelp: | ||
| "This is a mini-tutorial. Use the previous and next buttons to navigate. Once completed, you'll enter the recording portal.", | ||
| icon: PartyPopper, | ||
| title: "Welcome to Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane helps people gather input from large groups easily.", | ||
| cta: "Tell me more", | ||
| extraHelp: | ||
| "Whether it's feedback for a local municipality, input in a work setting, or participation in research, your voice matters!", | ||
| icon: Orbit, | ||
| title: "What is Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Answer questions in your own time by speaking or typing.", | ||
| cta: "Next", | ||
| extraHelp: | ||
| "Voice input is our primary mode, allowing for more natural and detailed responses. Typing is always available as a backup.", | ||
| icon: Speech, | ||
| title: "Just Speak Your Mind", | ||
| }, | ||
| { | ||
| content: "Dembrane is more fun in groups!", | ||
| cta: "Next", | ||
| extraHelp: | ||
| "Dembrane is more fun when you find someone to discuss the questions together and record your conversation. We can't tell who said what, just what ideas were shared.", | ||
| icon: MessagesSquare, | ||
| title: "Solo or in a Group", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "How it works", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "You'll receive the questions once in the recording portal.", | ||
| cta: "Got it", | ||
| extraHelp: | ||
| "Questions vary based on the host's needs. They could be about community issues, work experiences, or research topics. If there are no specific questions, you're free to share any thoughts or concerns.", | ||
| icon: HelpCircle, | ||
| title: "Question Time", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacy", | ||
| slides: [ | ||
| { | ||
| content: "As the recorder, you are in control of what you share.", | ||
| cta: "Tell me more", | ||
| extraHelp: | ||
| "Avoid sharing details you don't want the host to know. Be mindful and don't record others without their consent.", | ||
| icon: Lock, | ||
| title: "Privacy Matters", | ||
| }, | ||
| ...(getPrivacyCard("en-US")?.slides || []), | ||
| ], | ||
| }, | ||
| { | ||
| section: "Best Practices", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Imagine Dembrane is on speakerphone with you. If you can hear yourself, you're good to go.", | ||
| cta: "Noted", | ||
| extraHelp: | ||
| "Some background noise is okay, as long as who is speaking is clear.", | ||
| icon: Volume2, | ||
| title: "Reduce Background Noise", | ||
| }, | ||
| { | ||
| content: "Ensure a stable connection for smooth recording.", | ||
| cta: "Ready!", | ||
| extraHelp: | ||
| "Wi-Fi or good mobile data works best. If your connection drops, don't worry. You can always restart where you left off.", | ||
| icon: Wifi, | ||
| title: "Strong Internet Connection", | ||
| }, | ||
| { | ||
| content: | ||
| "Prevent interruptions by keeping your device unlocked. If it locks, just unlock and continue.", | ||
| cta: "Okay", | ||
| extraHelp: | ||
| "Dembrane tries to keep your device active, but sometimes devices can override it, for example if you have low power mode active. You can adjust your device settings to stay unlocked longer if needed.", | ||
| icon: Smartphone, | ||
| title: "Don't lock your device!", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "es-ES": [ | ||
| { | ||
| section: "Bienvenido", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Graba tu voz para responder preguntas y generar impacto.", | ||
| cta: "¡Vamos!", | ||
| extraHelp: | ||
| "Este es un mini-tutorial. Usa los botones de anterior y siguiente para navegar. Una vez completado, entrarás al portal de grabación.", | ||
| icon: PartyPopper, | ||
| title: "¡Bienvenido a Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane ayuda a las personas a recopilar aportaciones de grandes grupos fácilmente.", | ||
| cta: "Cuéntame más", | ||
| extraHelp: | ||
| "Ya sea retroalimentación para una municipalidad local, aportaciones en el trabajo o participación en investigación, ¡tu voz importa!", | ||
| icon: Orbit, | ||
| title: "¿Qué es Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Responde preguntas a tu propio ritmo hablando o escribiendo.", | ||
| cta: "Siguiente", | ||
| extraHelp: | ||
| "La entrada de voz es nuestro modo principal, permitiendo respuestas más naturales y detalladas. Escribir siempre está disponible como respaldo.", | ||
| icon: Speech, | ||
| title: "Solo Di Lo Que Piensas", | ||
| }, | ||
| { | ||
| content: "¡Dembrane es más divertido en grupos!", | ||
| cta: "Siguiente", | ||
| extraHelp: | ||
| "Dembrane es más divertido cuando encuentras a alguien para discutir las preguntas juntos y grabar su conversación. No podemos distinguir quién dijo qué, solo qué ideas se compartieron.", | ||
| icon: MessagesSquare, | ||
| title: "Solo o en Grupo", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Cómo funciona", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Recibirás las preguntas una vez en el portal de grabación.", | ||
| cta: "Entendido", | ||
| extraHelp: | ||
| "Las preguntas varían según las necesidades del anfitrión. Pueden ser sobre temas comunitarios, experiencias laborales o temas de investigación. Si no hay preguntas específicas, eres libre de compartir cualquier pensamiento o inquietud.", | ||
| icon: HelpCircle, | ||
| title: "Hora de Preguntas", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacidad", | ||
| slides: [ | ||
| { | ||
| content: "Como grabador, tú controlas lo que compartes.", | ||
| cta: "Cuéntame más", | ||
| extraHelp: | ||
| "Evita compartir detalles que no quieras que el anfitrión conozca. Sé consciente y no grabes a otros sin su consentimiento.", | ||
| icon: Lock, | ||
| title: "La Privacidad Importa", | ||
| }, | ||
| ...(getPrivacyCard("es-ES")?.slides || []), | ||
| ], | ||
| }, | ||
| { | ||
| section: "Mejores Prácticas", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Imagina que Dembrane está en altavoz contigo. Si puedes escucharte, estás listo.", | ||
| cta: "Entendido", | ||
| extraHelp: | ||
| "Un poco de ruido de fondo está bien, siempre que se entienda quién está hablando.", | ||
| icon: Volume2, | ||
| title: "Reduce el Ruido de Fondo", | ||
| }, | ||
| { | ||
| content: | ||
| "Asegura una conexión estable para una grabación fluida.", | ||
| cta: "¡Listo!", | ||
| extraHelp: | ||
| "Wi-Fi o buenos datos móviles funcionan mejor. Si se cae tu conexión, no te preocupes. Siempre puedes reiniciar donde lo dejaste.", | ||
| icon: Wifi, | ||
| title: "Conexión a Internet Fuerte", | ||
| }, | ||
| { | ||
| content: | ||
| "Evita interrupciones manteniendo tu dispositivo desbloqueado. Si se bloquea, simplemente desbloquéalo y continúa.", | ||
| cta: "De acuerdo", | ||
| extraHelp: | ||
| "Dembrane intenta mantener tu dispositivo activo, pero a veces los dispositivos pueden anularlo. Puedes ajustar la configuración de tu dispositivo para permanecer desbloqueado más tiempo si es necesario.", | ||
| icon: Smartphone, | ||
| title: "¡No bloquees tu dispositivo!", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "fr-FR": [ | ||
| { | ||
| section: "Bienvenue", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Enregistrez votre voix pour répondre aux questions et avoir un impact.", | ||
| cta: "C'est parti !", | ||
| extraHelp: | ||
| "Ceci est un mini-tutoriel. Utilisez les boutons précédent et suivant pour naviguer. Une fois terminé, vous entrerez dans le portail d'enregistrement.", | ||
| icon: PartyPopper, | ||
| title: "Bienvenue sur Dembrane !", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane aide les gens à recueillir facilement les contributions de grands groupes.", | ||
| cta: "Dites-m'en plus", | ||
| extraHelp: | ||
| "Qu'il s'agisse de commentaires pour une municipalité locale, de contributions dans un cadre professionnel ou de participation à une recherche, votre voix compte !", | ||
| icon: Orbit, | ||
| title: "Qu'est-ce que Dembrane ?", | ||
| }, | ||
| { | ||
| content: | ||
| "Répondez aux questions à votre rythme en parlant ou en tapant.", | ||
| cta: "Suivant", | ||
| extraHelp: | ||
| "La saisie vocale est notre mode principal, permettant des réponses plus naturelles et détaillées. La saisie au clavier est toujours disponible en secours.", | ||
| icon: Speech, | ||
| title: "Dites Simplement Ce Que Vous Pensez", | ||
| }, | ||
| { | ||
| content: "Dembrane est plus amusant en groupe !", | ||
| cta: "Suivant", | ||
| extraHelp: | ||
| "Dembrane est plus amusant lorsque vous trouvez quelqu'un pour discuter des questions ensemble et enregistrer votre conversation. Nous ne pouvons pas dire qui a dit quoi, juste quelles idées ont été partagées.", | ||
| icon: MessagesSquare, | ||
| title: "Seul ou en Groupe", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Comment ça marche", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Vous recevrez les questions une fois dans le portail d'enregistrement.", | ||
| cta: "Compris", | ||
| extraHelp: | ||
| "Les questions varient en fonction des besoins de l'hôte. Elles peuvent concerner des problèmes communautaires, des expériences professionnelles ou des sujets de recherche. S'il n'y a pas de questions spécifiques, vous êtes libre de partager vos pensées ou préoccupations.", | ||
| icon: HelpCircle, | ||
| title: "Heure des Questions", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Confidentialité", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "En tant qu'enregistreur, vous contrôlez ce que vous partagez.", | ||
| cta: "Dites-m'en plus", | ||
| extraHelp: | ||
| "Évitez de partager des détails que vous ne voulez pas que l'hôte connaisse. Soyez attentif et n'enregistrez pas les autres sans leur consentement.", | ||
| icon: Lock, | ||
| title: "La Confidentialité Compte", | ||
| }, | ||
| ...(getPrivacyCard("fr-FR")?.slides || []), | ||
| ], | ||
| }, | ||
| { | ||
| section: "Meilleures Pratiques", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Imaginez que Dembrane est sur haut-parleur avec vous. Si vous pouvez vous entendre, c'est bon.", | ||
| cta: "Noté", | ||
| extraHelp: | ||
| "Un peu de bruit de fond est acceptable, tant qu'on sait qui parle.", | ||
| icon: Volume2, | ||
| title: "Réduire le Bruit de Fond", | ||
| }, | ||
| { | ||
| content: | ||
| "Assurez une connexion stable pour un enregistrement fluide.", | ||
| cta: "Prêt !", | ||
| extraHelp: | ||
| "Le Wi-Fi ou de bonnes données mobiles fonctionnent mieux. Si votre connexion tombe, ne vous inquiétez pas. Vous pouvez toujours reprendre là où vous vous êtes arrêté.", | ||
| icon: Wifi, | ||
| title: "Connexion Internet Forte", | ||
| }, | ||
| { | ||
| content: | ||
| "Évitez les interruptions en gardant votre appareil déverrouillé. S'il se verrouille, déverrouillez-le simplement et continuez.", | ||
| cta: "D'accord", | ||
| extraHelp: | ||
| "Dembrane essaie de garder votre appareil actif, mais parfois les appareils peuvent l'annuler. Vous pouvez ajuster les paramètres de votre appareil pour rester déverrouillé plus longtemps si nécessaire.", | ||
| icon: Smartphone, | ||
| title: "Ne verrouillez pas votre appareil !", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| "nl-NL": [ | ||
| { | ||
| section: "Welkom", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Neem je stem op om vragen te beantwoorden en impact te maken.", | ||
| cta: "Aan de slag!", | ||
| extraHelp: | ||
| "Dit is een mini-handleiding. Gebruik de knoppen om te navigeren. Na afloop van de handleiding kom je in de opnameportaal terecht.", | ||
| icon: PartyPopper, | ||
| title: "Welkom bij Dembrane!", | ||
| }, | ||
| { | ||
| content: | ||
| "Dembrane helpt mensen gemakkelijk input van grote groepen te verzamelen.", | ||
| cta: "Vertel me meer", | ||
| extraHelp: | ||
| "Of het nu gaat om feedback voor de gemeente, input op het werk, of deelname aan onderzoek, jouw stem telt!", | ||
| icon: Orbit, | ||
| title: "Wat is Dembrane?", | ||
| }, | ||
| { | ||
| content: | ||
| "Beantwoord vragen in je eigen tempo door te spreken of te typen.", | ||
| cta: "Volgende", | ||
| extraHelp: | ||
| "Spraak is onze voorkeursmethode, omdat het natuurlijker en gedetailleerder is. Typen kan natuurlijk ook altijd.", | ||
| icon: Speech, | ||
| title: "Zeg het maar", | ||
| }, | ||
| { | ||
| content: "Dembrane is leuker met een groep!", | ||
| cta: "Volgende", | ||
| extraHelp: | ||
| "Dembrane is leuker als je iemand vindt om de vragen samen te bespreken en jullie gesprek op te nemen. We kunnen niet horen wie wat zei, alleen welke ideeën er gedeeld zijn.", | ||
| icon: MessagesSquare, | ||
| title: "Alleen of in een groep", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Hoe het werkt", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Je krijgt de vragen te zien zodra je in de opnameportal bent.", | ||
| cta: "Begrepen", | ||
| extraHelp: | ||
| "Vragen variëren afhankelijk van wat de organisator wil weten. Het kan gaan over de buurt, werkervaringen, of onderzoeksonderwerpen. Als er geen specifieke vragen zijn, kun je gewoon je gedachten of zorgen delen.", | ||
| icon: HelpCircle, | ||
| title: "Vragenronde", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| section: "Privacy", | ||
| slides: [ | ||
| { | ||
| content: "Als opnemer heb je zelf controle over wat je deelt.", | ||
| cta: "Vertel me meer", | ||
| extraHelp: | ||
| "Vermijd het delen van details die je niet met de organisator wilt delen. Wees voorzichtig en neem anderen niet op zonder hun toestemming.", | ||
| icon: Lock, | ||
| title: "Privacy is belangrijk", | ||
| }, | ||
| ...(getPrivacyCard("nl-NL")?.slides || []), | ||
| ], | ||
| }, | ||
| { | ||
| section: "Tips", | ||
| slides: [ | ||
| { | ||
| content: | ||
| "Stel je voor dat Dembrane via de luidspreker met je praat. Als je jezelf kunt horen, zit je goed.", | ||
| cta: "Begrepen", | ||
| extraHelp: | ||
| "Een beetje achtergrondgeluid is geen probleem, zolang duidelijk is wie er spreekt.", | ||
| icon: Volume2, | ||
| title: "Verminder achtergrondgeluid", | ||
| }, | ||
| { | ||
| content: | ||
| "Zorg voor een stabiele verbinding voor een soepele opname.", | ||
| cta: "Klaar!", | ||
| extraHelp: | ||
| "Wi-Fi of een goede mobiele verbinding werkt het beste. Valt je verbinding weg? Geen zorgen, je kunt altijd opnieuw beginnen waar je gebleven was.", | ||
| icon: Wifi, | ||
| title: "Goede internetverbinding", | ||
| }, | ||
| { | ||
| content: | ||
| "Voorkom onderbrekingen door je apparaat ontgrendeld te houden. Als het toch vergrendelt, ontgrendel je het gewoon en ga je verder.", | ||
| cta: "Oké", | ||
| extraHelp: | ||
| "Dembrane probeert je apparaat actief te houden, maar soms kunnen apparaten dit overrulen. Je kunt de instellingen van je apparaat aanpassen om langer ontgrendeld te blijven als dat nodig is.", | ||
| icon: Smartphone, | ||
| title: "Vergrendel je apparaat niet!", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| // Fallback to English if language not found | ||
| return tutorialCards[lang] || tutorialCards["en-US"] || []; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Refactor opportunity: Massive code duplication between basic and advanced tutorials.
The Welcome, How it works, and initial Privacy sections are 100% identical across basic and advanced for all 5 languages (~900 lines of duplication). This makes maintenance painful and error-prone.
Extract shared sections into a helper:
+const getSharedTutorialSections = (lang: string): LanguageCards[string] => {
+ const sections: Record<string, LanguageCards[string]> = {
+ "en-US": [
+ {
+ section: "Welcome",
+ slides: [/* Welcome slides */]
+ },
+ {
+ section: "How it works",
+ slides: [/* How it works slides */]
+ }
+ ],
+ // ... other languages
+ };
+ return sections[lang] || sections["en-US"] || [];
+};
const getBasicTutorialCards = (lang: string): LanguageCards[string] => {
- const tutorialCards: Record<string, LanguageCards[string]> = {
- "en-US": [/* full sections */],
- // ...
- };
- return tutorialCards[lang] || tutorialCards["en-US"] || [];
+ const sharedSections = getSharedTutorialSections(lang);
+ const privacyCard = getPrivacyCard(lang);
+ return [...sharedSections, ...(privacyCard ? [privacyCard] : [])];
};
const getAdvancedTutorialCards = (lang: string): LanguageCards[string] => {
+ const sharedSections = getSharedTutorialSections(lang);
+ const bestPractices = getBestPracticesSections(lang);
+ // Merge privacy slides into last shared section
+ const privacySlides = getPrivacyCard(lang)?.slides || [];
+ sharedSections[sharedSections.length - 1].slides.push(...privacySlides);
+ return [...sharedSections, bestPractices];
};This cuts ~900 lines and makes updates to shared content way easier.
Committable suggestion skipped: line range outside the PR's diff.
| project.default_conversation_transcript_prompt ?? "", | ||
| default_conversation_tutorial_slug: | ||
| project.default_conversation_tutorial_slug ?? "none", | ||
| default_conversation_tutorial_slug: normalizedTutorialSlug ?? "none", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Double fallback ?? "none" is redundant but harmless.
normalizedTutorialSlug is already guaranteed to be a valid string from the ternary above. The ?? "none" is belt-and-suspenders but doesn't hurt.
- default_conversation_tutorial_slug: normalizedTutorialSlug ?? "none",
+ default_conversation_tutorial_slug: normalizedTutorialSlug,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| default_conversation_tutorial_slug: normalizedTutorialSlug ?? "none", | |
| default_conversation_tutorial_slug: normalizedTutorialSlug, |
🤖 Prompt for AI Agents
In echo/frontend/src/components/project/ProjectPortalEditor.tsx around line 247,
the property default_conversation_tutorial_slug uses a redundant nullish
coalescing fallback (?? "none") even though normalizedTutorialSlug is already
guaranteed to be a valid string by the preceding ternary; remove the extraneous
?? "none" so the assignment becomes default_conversation_tutorial_slug:
normalizedTutorialSlug to eliminate unnecessary duplication.
| #: src/components/project/ProjectPortalEditor.tsx:543 | ||
| msgid "Advanced (Tips and best practices)" | ||
| msgstr "Erweitert (Tipps und best practices)" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find the file
find . -name "de-DE.po" -path "*/locales/*" | head -5Repository: Dembrane/echo
Length of output: 93
🏁 Script executed:
#!/bin/bash
# Read the flagged sections from de-DE.po
# Using sed to read specific line ranges
echo "=== Lines 229-232 ==="
sed -n '229,232p' ./echo/frontend/src/locales/de-DE.po
echo ""
echo "=== Lines 496-499 ==="
sed -n '496,499p' ./echo/frontend/src/locales/de-DE.po
echo ""
echo "=== Lines 531-536 ==="
sed -n '531,536p' ./echo/frontend/src/locales/de-DE.po
echo ""
echo "=== Lines 651-656 ==="
sed -n '651,656p' ./echo/frontend/src/locales/de-DE.po
echo ""
echo "=== Lines 703-707 ==="
sed -n '703,707p' ./echo/frontend/src/locales/de-DE.poRepository: Dembrane/echo
Length of output: 1275
🏁 Script executed:
#!/bin/bash
# Search for patterns related to slides, cards, and privacy terminology
echo "=== Searching for 'slide' or 'Karte' (card) terminology ==="
rg -i "(slide|karte|privacy|datenschutz)" ./echo/frontend/src/locales/de-DE.po -A 2 -B 2
echo ""
echo "=== Searching for 'best practices' ==="
rg "best practices" ./echo/frontend/src/locales/de-DE.po -A 2 -B 2Repository: Dembrane/echo
Length of output: 1404
Minor terminology inconsistencies in tutorial and privacy-related labels
The German translations have two opportunities for alignment:
- "best practices" in line 229 is left untranslated while other UI strings are localized; consider a German equivalent (e.g., "bewährte Praktiken")
- "data privacy slide" (line 531) uses "Karte", while "tutorial slides" (line 496) uses "Folien"—consider standardizing on one term for consistency
Non-blocking; ideal for a future copy review when focusing on localization polish.
🤖 Prompt for AI Agents
In echo/frontend/src/locales/de-DE.po around lines 229 and referencing lines
~496 and ~531, the German translation leaves "best practices" untranslated and
uses inconsistent terms for tutorial/privacy slides; replace "Erweitert (Tipps
und best practices)" with a fully German string such as "Erweitert (Tipps und
bewährte Praktiken)" and standardize the term for slides across the file (prefer
using "Folien" instead of "Karte") so all tutorial and data-privacy related
entries use the same German word for "slides."
| #. js-lingui-explicit-id | ||
| #: src/components/participant/ParticipantOnboardingCards.tsx:291 | ||
| #: src/components/participant/ParticipantOnboardingCards.tsx:281 | ||
| msgid "participant.mic.check.button.skip" | ||
| msgstr "Overslaan" | ||
|
|
||
| #~ msgid "Skip" | ||
| #~ msgstr "Overslaan" | ||
|
|
||
| #: src/components/project/ProjectPortalEditor.tsx:531 | ||
| msgid "Skip data privacy slide (Host manages consent)" | ||
| msgstr "Datenschutzkarte overslaan (Organisatie beheert toestemming)" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix mixed-language translation for “Skip data privacy slide…”.
Right now the msgstr mixes German and Dutch ("Datenschutzkarte overslaan..."), which is going to look off in the UI. I’d tighten it up to native Dutch:
-msgstr "Datenschutzkarte overslaan (Organisatie beheert toestemming)"
+msgstr "Sla de privacydia over (Organisatie beheert toestemming)"🤖 Prompt for AI Agents
In echo/frontend/src/locales/nl-NL.po around lines 3217-3228, the msgstr for
"Skip data privacy slide (Host manages consent)" mixes German and Dutch; replace
the mixed-language string with a proper Dutch translation such as "Privacy-dia
overslaan (Host beheert toestemming)" (or "Privacy-slide overslaan (Host beheert
toestemming)") so the entire msgstr is native Dutch and consistent with the
msgid.
| #: src/components/project/ProjectPortalEditor.tsx:729 | ||
| msgid "This prompt guides how the AI responds to participants. Customize it to shape the type of feedback or engagement you want to encourage." | ||
| msgstr "Deze prompt bepaalt hoe de AI reageert op deelnemers. Deze prompt stuurt aan hoe de AI reageert" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Complete the “This prompt guides…” translation; current msgstr is truncated.
The Dutch copy for the reply prompt helper stops mid-thought and repeats itself, so it doesn’t explain what the prompt actually does. Something like this would be closer to the English intent:
-msgstr "Deze prompt bepaalt hoe de AI reageert op deelnemers. Deze prompt stuurt aan hoe de AI reageert"
+msgstr "Deze prompt bepaalt hoe de AI reageert op deelnemers. Pas hem aan om het type feedback of betrokkenheid te sturen dat je wilt stimuleren."This keeps the tone and gives hosts a clear mental model of what changing the prompt does.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #: src/components/project/ProjectPortalEditor.tsx:729 | |
| msgid "This prompt guides how the AI responds to participants. Customize it to shape the type of feedback or engagement you want to encourage." | |
| msgstr "Deze prompt bepaalt hoe de AI reageert op deelnemers. Deze prompt stuurt aan hoe de AI reageert" | |
| #: src/components/project/ProjectPortalEditor.tsx:729 | |
| msgid "This prompt guides how the AI responds to participants. Customize it to shape the type of feedback or engagement you want to encourage." | |
| msgstr "Deze prompt bepaalt hoe de AI reageert op deelnemers. Pas hem aan om het type feedback of betrokkenheid te sturen dat je wilt stimuleren." |
🤖 Prompt for AI Agents
In echo/frontend/src/locales/nl-NL.po around lines 3627 to 3630, the msgstr for
the msgid starting "This prompt guides how the AI responds to participants..."
is truncated; replace the current incomplete Dutch string with a full, complete
translation that matches the English intent — for example: "Deze prompt bepaalt
hoe de AI reageert op deelnemers. Pas deze prompt aan om te sturen welk soort
feedback of betrokkenheid je wilt aanmoedigen." — update the msgstr value to
this full sentence so the helper explains clearly what changing the prompt does.
| {isLoadingProject ? ( | ||
| <DembraneLoadingSpinner isLoading /> | ||
| ) : ( | ||
| <ParticipantOnboardingCards | ||
| initialCards={transformCard(tutorialCard as EchoPortalTutorial)} | ||
| project={project as Project} | ||
| /> | ||
| <ParticipantOnboardingCards project={project as Project} /> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion project as Project needs a guard.
If isLoadingProject is false but project is undefined (edge case on fetch failure without error), you'll pass undefined to ParticipantOnboardingCards. The error check at line 32 only fires when projectError is truthy.
Consider adding a null check:
return (
<div className="h-full grow">
{isLoadingProject ? (
<DembraneLoadingSpinner isLoading />
+ ) : !project ? (
+ <div className="flex flex-col items-center justify-center">
+ <Alert color="info">
+ {t`An error occurred while loading the Portal. Please contact the support team.`}
+ </Alert>
+ </div>
) : (
<ParticipantOnboardingCards project={project as Project} />
)}
</div>
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {isLoadingProject ? ( | |
| <DembraneLoadingSpinner isLoading /> | |
| ) : ( | |
| <ParticipantOnboardingCards | |
| initialCards={transformCard(tutorialCard as EchoPortalTutorial)} | |
| project={project as Project} | |
| /> | |
| <ParticipantOnboardingCards project={project as Project} /> | |
| )} | |
| return ( | |
| <div className="h-full grow"> | |
| {isLoadingProject ? ( | |
| <DembraneLoadingSpinner isLoading /> | |
| ) : !project ? ( | |
| <div className="flex flex-col items-center justify-center"> | |
| <Alert color="info"> | |
| {t`An error occurred while loading the Portal. Please contact the support team.`} | |
| </Alert> | |
| </div> | |
| ) : ( | |
| <ParticipantOnboardingCards project={project as Project} /> | |
| )} | |
| </div> | |
| ); |
🤖 Prompt for AI Agents
In echo/frontend/src/routes/participant/ParticipantStart.tsx around lines 44-48,
the code force-casts project with "project as Project" even when
isLoadingProject is false, which can pass undefined into
ParticipantOnboardingCards; update the conditional to guard against a missing
project (e.g., change the ternary to check both isLoadingProject and project
existence, or render an error/fallback UI when project is undefined) and remove
the unsafe type assertion so ParticipantOnboardingCards only receives a valid
Project instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have this guard: if (loadingFinished && projectError) {
return (
{t
An error occurred while loading the Portal. Please contact the support team.});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ussaama Ah yeah, I see the guard! But there's a subtle timing issue – loadingFinished is set in the useEffect, so on the first render after the query errors out, you have:
isLoadingProject:falseloadingFinished: stillfalse(hasn't updated yet)projectError: truthy
So the guard if (loadingFinished && projectError) doesn't fire, and you fall through to rendering project as Project while it's undefined.
Quick fix: just drop the loadingFinished check:
if (projectError) {
return (
<div className="flex flex-col items-center justify-center">
<Alert color="info">
{t`An error occurred while loading the Portal. Please contact the support team.`}
</Alert>
</div>
);
}Or if you need loadingFinished for the session storage flag, add a null check in the render ternary. LGTM otherwise! 🚀
🧠 Learnings used
Learnt from: ussaama
Repo: Dembrane/echo PR: 259
File: echo/frontend/src/components/layout/ParticipantLayout.tsx:33-33
Timestamp: 2025-08-08T10:39:31.114Z
Learning: In echo/frontend/src/components/layout/ParticipantLayout.tsx, prefer using simple pathname.includes("start") and pathname.includes("finish") to control the settings button visibility. No need to switch to segment-based matching or add a useEffect to auto-close the modal for these routes, per ussaama’s preference in PR #259.
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Auth hero uses `/public/video/auth-hero.mp4` with `/public/video/auth-hero-poster.jpg` as poster; keep the bright blur overlay consistent when iterating on onboarding screens
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.442Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Gentle login/logout flows use `useTransitionCurtain().runTransition()` before navigation—animations expect Directus session mutations to await that promise
Learnt from: ussaama
Repo: Dembrane/echo PR: 169
File: echo/frontend/src/components/project/ProjectPortalEditor.tsx:409-464
Timestamp: 2025-05-30T15:38:44.413Z
Learning: Badge-based selectors in ProjectPortalEditor.tsx: Keyboard navigation enhancements for accessibility are considered optional improvements rather than critical issues. The user acknowledges these suggestions but doesn't prioritize them as blockers.
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses regular useQuery (not useSuspenseQuery), which means conversationsCountQuery.data can be undefined during loading states. When using Number(conversationsCountQuery.data) ?? 0, this creates NaN because Number(undefined) = NaN and NaN is not nullish, so the fallback doesn't apply. The correct pattern is Number(conversationsCountQuery.data ?? 0) to ensure the fallback happens before type conversion.
Learnt from: ussaama
Repo: Dembrane/echo PR: 349
File: echo/frontend/src/components/report/hooks/index.ts:51-71
Timestamp: 2025-10-24T08:08:23.128Z
Learning: In echo/frontend/src/components/report/hooks/index.ts, the useGetProjectParticipants hook uses Directus aggregate which always returns count as a string (e.g., "0", "1", "2"). The pattern `Number.parseInt(result[0]?.count ?? "0", 10) || 0` is the correct way to handle this, with the string fallback "0" ensuring parseInt receives a valid string input.
<!--
Learnt from: ussaama
Repo: Dembrane/echo PR: 205
File: echo/frontend/src/lib/query.ts:1444-1506
Timestamp: 2025-07-10T12:48:20.683Z
Learning: ussaama prefers string concatenation over template literals for simple cases where readability is clearer, even when linting tools suggest template literals. Human readability takes precedence over strict linting rules in straightforward concatenation scenarios.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.