AbiirSkincare/Hydrogen/web/app/components/AbiirSkincareQuiz.tsx

203 lines
8.3 KiB
TypeScript

import {useMemo, useState} from 'react';
import {Link} from 'react-router';
import {CartForm} from '@shopify/hydrogen';
import {AbiirProductListItem} from '~/components/AbiirProductListItem';
import {Shell} from '~/components/AbiirStorePages';
import type {AbiirInfoPageCms} from '~/lib/abiirStaticPages';
import {ABIIR_PRODUCT_LIST_GRID_CLASS} from '~/lib/abiirProductListGrid';
import type {AbiirProductListItemData} from '~/lib/featuredProducts';
import {
ABIIR_QUIZ_PRODUCT_HANDLES_IN_ORDER,
ABIIR_SKINCARE_QUIZ_QUESTIONS,
getMatchingQuizProductHandles,
type QuizLetter,
} from '~/lib/abiirSkincareQuiz';
const TOTAL = 7;
const optionButtonClass =
"flex w-full cursor-pointer items-center gap-3 border border-[rgba(26,26,24,0.12)] bg-[#f8f4ef] px-4 py-3.5 text-left font-['Jost',sans-serif] text-[0.95rem] font-normal not-italic leading-snug text-[#1a1a18] transition-colors hover:border-[#c17d5c] hover:bg-[#ede8e0] sm:px-5 sm:py-4";
const optionLetterClass =
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-[rgba(26,26,24,0.15)] bg-[#ede8e0] font-['Jost',sans-serif] text-[0.75rem] font-medium text-[#5d0b24]";
type Props = {
cms: AbiirInfoPageCms;
quizProducts: AbiirProductListItemData[];
};
export function AbiirSkincareQuiz({cms, quizProducts}: Props) {
const [step, setStep] = useState(0);
/** Index 0..6 → selected letter when that question was answered */
const [answers, setAnswers] = useState<(QuizLetter | null)[]>(() =>
Array.from({length: TOTAL}, () => null),
);
const isResults = step >= TOTAL;
const productByHandle = useMemo(() => {
const m = new Map<string, AbiirProductListItemData>();
for (const row of quizProducts) {
const h = row.handle?.trim();
if (h) m.set(h, row);
}
return m;
}, [quizProducts]);
const recommendedRows = useMemo(() => {
if (!isResults) return [];
if (answers.some((a) => a == null)) return [];
const letters = answers as readonly QuizLetter[];
const handles = getMatchingQuizProductHandles(letters);
const set = new Set(handles);
const out: AbiirProductListItemData[] = [];
for (const h of ABIIR_QUIZ_PRODUCT_HANDLES_IN_ORDER) {
if (set.has(h)) {
const row = productByHandle.get(h);
if (row) out.push(row);
}
}
return out;
}, [answers, isResults, productByHandle]);
const linesToAddAll = useMemo(
() =>
recommendedRows
.filter((r) => Boolean(r.firstVariantId) && r.availableForSale !== false)
.map((r) => ({merchandiseId: r.firstVariantId as string, quantity: 1})),
[recommendedRows],
);
function choose(letter: QuizLetter) {
setAnswers((prev) => {
const next = [...prev];
next[step] = letter;
return next;
});
setStep((s) => s + 1);
}
function goBack() {
if (step <= 0) return;
if (isResults) {
setStep(TOTAL - 1);
return;
}
setStep((s) => s - 1);
setAnswers((prev) => {
const next = [...prev];
next[step - 1] = null;
return next;
});
}
const q = !isResults ? ABIIR_SKINCARE_QUIZ_QUESTIONS[step] : null;
return (
<Shell eyebrow="Help" subtitle={cms.intro || undefined} title={cms.title}>
<div
className={`mx-auto w-full text-left ${isResults ? 'max-w-[1200px]' : 'max-w-[640px]'}`}
>
{!isResults && q ? (
<>
<p className="m-0 font-['Jost',sans-serif] text-[0.7rem] font-medium uppercase not-italic tracking-[0.14em] text-[#c17d5c]">
Question {step + 1} of {TOTAL}
</p>
<h2 className="m-0 mt-3 font-['Cormorant_Garamond',serif] text-[1.65rem] font-light not-italic leading-tight tracking-[-0.02em] text-[#1a1a18] sm:text-[1.85rem]">
{q.prompt}
</h2>
<div className="mt-8 flex flex-col gap-2.5 sm:gap-3">
{q.options.map((opt) => (
<button
className={optionButtonClass}
key={opt.letter}
onClick={() => choose(opt.letter)}
type="button"
>
<span className={optionLetterClass}>{opt.letter}</span>
<span className="min-w-0 flex-1">{opt.label}</span>
</button>
))}
</div>
{step > 0 ? (
<div className="mt-10">
<button
className="cursor-pointer border-0 bg-transparent font-['Jost',sans-serif] text-[0.8rem] font-medium uppercase tracking-[0.12em] text-[#7a7068] underline-offset-4 transition-colors hover:text-[#1a1a18] hover:underline"
onClick={goBack}
type="button"
>
Back
</button>
</div>
) : null}
</>
) : (
<>
<h2 className="m-0 font-['Cormorant_Garamond',serif] text-[1.65rem] font-light not-italic leading-tight tracking-[-0.02em] text-[#1a1a18] sm:text-[1.85rem]">
Your picks
</h2>
<p className="m-0 mt-3 font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.55] text-[#5a5550]">
Based on your answers, here are products we think you will love.
</p>
{recommendedRows.length ? (
<div className={`${ABIIR_PRODUCT_LIST_GRID_CLASS} mt-10`}>
{recommendedRows.map((item, index) => (
<AbiirProductListItem index={index} item={item} key={item.handle} />
))}
</div>
) : (
<p className="m-0 mt-6 font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.55] text-[#5a5550]">
We could not match a preset bundle to every combination. Explore the full line or
reach out—we are happy to help you build a ritual.
</p>
)}
<div className="mt-10 flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center sm:gap-5">
{linesToAddAll.length ? (
<CartForm
route="/cart"
action={CartForm.ACTIONS.LinesAdd}
className="w-full sm:w-auto"
inputs={{lines: linesToAddAll}}
>
{(fetcher) => (
<button
className="flex min-h-[48px] w-full cursor-pointer items-center justify-center border border-[#5d0b24] bg-[#5d0b24] px-7 py-3 font-['Raleway',sans-serif] text-[12px] font-semibold uppercase tracking-[2.2px] text-[#f8f4ef] transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60 sm:inline-flex sm:w-auto"
disabled={fetcher.state !== 'idle'}
type="submit"
>
Add all products to Cart
</button>
)}
</CartForm>
) : (
<button
className="flex min-h-[48px] w-full cursor-not-allowed items-center justify-center border border-[rgba(26,26,24,0.2)] bg-[#ede8e0] px-7 py-3 font-['Raleway',sans-serif] text-[12px] font-semibold uppercase tracking-[2.2px] text-[#7a7068] opacity-70 sm:inline-flex sm:w-auto"
disabled
type="button"
>
Add all products to Cart
</button>
)}
<Link
className="inline-flex min-h-[48px] cursor-pointer items-center justify-center border border-[rgba(26,26,24,0.12)] bg-[#ede8e0] px-7 py-3 font-['Jost',sans-serif] text-[0.75rem] font-medium uppercase tracking-[0.14em] text-[#1a1a18] no-underline transition-colors hover:border-[#c17d5c] hover:bg-[#e5dfd4]"
to="/collections/all"
>
Shop all products
</Link>
</div>
<div className="mt-6">
<button
className="cursor-pointer border-0 bg-transparent font-['Jost',sans-serif] text-[0.8rem] font-medium uppercase tracking-[0.12em] text-[#7a7068] underline-offset-4 transition-colors hover:text-[#1a1a18] hover:underline"
onClick={goBack}
type="button"
>
Back to last question
</button>
</div>
</>
)}
</div>
</Shell>
);
}