From c36ceb7ed9fec492a24dc62c5544ecd797bdad5a Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Fri, 30 Jan 2026 18:34:02 +0100 Subject: [PATCH] Add onboarding --- .../answer-compatibility-question-button.tsx | 106 ++++++-- web/components/bio/profile-bio-block.tsx | 23 +- web/components/optional-profile-form.tsx | 200 +++++++-------- web/components/profile/profile-header.tsx | 5 + web/messages/de.json | 45 ++++ web/messages/fr.json | 45 ++++ web/pages/onboarding/index.tsx | 227 ++++++++++++++++++ web/pages/onboarding/soft-gate.tsx | 74 ++++++ web/pages/register.tsx | 4 +- web/pages/signin.tsx | 2 +- web/styles/globals.css | 35 +++ 11 files changed, 633 insertions(+), 133 deletions(-) create mode 100644 web/pages/onboarding/index.tsx create mode 100644 web/pages/onboarding/soft-gate.tsx diff --git a/web/components/answers/answer-compatibility-question-button.tsx b/web/components/answers/answer-compatibility-question-button.tsx index a2443be..044b095 100644 --- a/web/components/answers/answer-compatibility-question-button.tsx +++ b/web/components/answers/answer-compatibility-question-button.tsx @@ -51,9 +51,10 @@ export function AnswerCompatibilityQuestionButton(props: { setOpen={setOpen} user={user} otherQuestions={questionsToAnswer} + fromSignup={fromSignup} refreshCompatibilityAll={refreshCompatibilityAll} onClose={() => { - if (fromSignup) router.push('/') + if (fromSignup) router.push('/onboarding/soft-gate') }} /> @@ -74,8 +75,9 @@ export function AnswerSkippedCompatibilityQuestionsButton(props: { user: User | null | undefined skippedQuestions: QuestionWithCountType[] refreshCompatibilityAll: () => void + fromSignup?: boolean }) { - const {user, skippedQuestions, refreshCompatibilityAll} = props + const {user, skippedQuestions, refreshCompatibilityAll, fromSignup} = props const [open, setOpen] = useState(false) const t = useT() if (!user) return null @@ -92,12 +94,56 @@ export function AnswerSkippedCompatibilityQuestionsButton(props: { setOpen={setOpen} user={user} otherQuestions={skippedQuestions} + fromSignup={fromSignup} refreshCompatibilityAll={refreshCompatibilityAll} /> ) } +function CompatibilityOnboardingScreen({onNext, onSkip}: { onNext: () => void; onSkip: () => void }) { + const t = useT() + + return ( + +

+ {t('compatibility.onboarding.title', 'See who you\'ll actually align with')} +

+ +
+

+ {t('compatibility.onboarding.body1', 'Answer a few short questions to calculate compatibility based on values and preferences — not photos or swipes.')} +

+

+ {t('compatibility.onboarding.body2', 'Your answers directly affect who matches with you and how strongly.')} +

+
+ +
+

+ {t('compatibility.onboarding.impact', 'Most people who answer at least 5 questions see far more relevant matches.')} +

+
+ + + + + + + ) +} + function AnswerCompatibilityQuestionModal(props: { open: boolean setOpen: (open: boolean) => void @@ -105,9 +151,21 @@ function AnswerCompatibilityQuestionModal(props: { otherQuestions: QuestionWithCountType[] refreshCompatibilityAll: () => void onClose?: () => void + fromSignup?: boolean }) { - const {open, setOpen, user, otherQuestions, refreshCompatibilityAll, onClose} = props + const {open, setOpen, user, otherQuestions, refreshCompatibilityAll, onClose, fromSignup} = props const [questionIndex, setQuestionIndex] = useState(0) + const [showOnboarding, setShowOnboarding] = useState(fromSignup ?? false) + + const handleStartQuestions = () => { + setShowOnboarding(false) + } + + const handleSkipOnboarding = () => { + setShowOnboarding(false) + setOpen(false) + } + return ( { refreshCompatibilityAll() setQuestionIndex(0) + setShowOnboarding(fromSignup ?? false) onClose?.() }} > - { - setOpen(false) - }} - isLastQuestion={questionIndex === otherQuestions.length - 1} - onNext={() => { - if (questionIndex === otherQuestions.length - 1) { + {showOnboarding ? ( + + ) : ( + { setOpen(false) - } else { - setQuestionIndex(questionIndex + 1) - } - }} - /> + }} + isLastQuestion={questionIndex === otherQuestions.length - 1} + onNext={() => { + if (questionIndex === otherQuestions.length - 1) { + setOpen(false) + } else { + setQuestionIndex(questionIndex + 1) + } + }} + /> + )} ) diff --git a/web/components/bio/profile-bio-block.tsx b/web/components/bio/profile-bio-block.tsx index e7a61c5..ab7ec61 100644 --- a/web/components/bio/profile-bio-block.tsx +++ b/web/components/bio/profile-bio-block.tsx @@ -1,16 +1,17 @@ -import { PencilIcon, XIcon } from '@heroicons/react/outline' -import { JSONContent } from '@tiptap/core' +import {PencilIcon, XIcon} from '@heroicons/react/outline' +import {JSONContent} from '@tiptap/core' import clsx from 'clsx' -import { Profile } from 'common/profiles/profile' +import {Profile} from 'common/profiles/profile' import DropdownMenu from 'web/components/comments/dropdown-menu' -import { Col } from 'web/components/layout/col' -import { Row } from 'web/components/layout/row' -import { Content } from 'web/components/widgets/editor' -import { updateProfile } from 'web/lib/api' -import { EditableBio } from './editable-bio' -import { tryCatch } from 'common/util/try-catch' -import { useT } from 'web/lib/locale' +import {Col} from 'web/components/layout/col' +import {Row} from 'web/components/layout/row' +import {Content} from 'web/components/widgets/editor' +import {updateProfile} from 'web/lib/api' +import {EditableBio} from './editable-bio' +import {tryCatch} from 'common/util/try-catch' +import {useT} from 'web/lib/locale' +import {Tooltip} from "web/components/widgets/tooltip"; export function BioBlock(props: { isCurrentUser: boolean @@ -46,6 +47,7 @@ export function BioBlock(props: { /> )} {isCurrentUser && !edit && ( + + )} diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index 221fab5..c9eaf32 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -488,26 +488,90 @@ export const OptionalProfileUserForm = (props: { } - + + + + + + + - {/**/} -
-
-
- setProfile('languages', selected)} - /> -
-
-
+ + + [t(`profile.education.${v}`, k), v]))} + setChoice={(c) => setProfile('education_level', c)} + /> + + + + setProfile('university', e.target.value)} + className={'w-52'} + value={profile['university'] ?? undefined} + /> + + + + + + + setProfile('occupation_title', e.target.value)} + className={'w-52'} + value={profile['occupation_title'] ?? undefined} + /> + + + + + setProfile('company', e.target.value)} + className={'w-52'} + value={profile['company'] ?? undefined} + /> + + + + @@ -550,26 +614,6 @@ export const OptionalProfileUserForm = (props: { /> - - - - - -