203 lines
8.3 KiB
TypeScript
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>
|
|
);
|
|
}
|