feat(tip): add personalized payment success experience
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, type CSSProperties } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Coffee, Heart, Sparkles } from "lucide-react";
|
||||
|
||||
import { CharacterAvatar } from "@/app/_components";
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
import type { TipCoffeeOption } from "@/lib/tip/tip_coffee";
|
||||
|
||||
import { resolveTipSuccessCopy } from "./tip-screen.helpers";
|
||||
import styles from "./tip-success-view.module.css";
|
||||
|
||||
export interface TipSuccessViewProps {
|
||||
characterName: string;
|
||||
characterAvatar: string;
|
||||
characterCover: string;
|
||||
coffeeOption: TipCoffeeOption;
|
||||
splashHref: string;
|
||||
tipCount: number | null;
|
||||
thankYouMessage: string | null;
|
||||
onSendAgain: () => void;
|
||||
}
|
||||
|
||||
export function TipSuccessView({
|
||||
characterName,
|
||||
characterAvatar,
|
||||
characterCover,
|
||||
coffeeOption,
|
||||
splashHref,
|
||||
tipCount,
|
||||
thankYouMessage,
|
||||
onSendAgain,
|
||||
}: TipSuccessViewProps) {
|
||||
const titleRef = useRef<HTMLHeadingElement>(null);
|
||||
const copy = resolveTipSuccessCopy(tipCount, thankYouMessage);
|
||||
|
||||
useEffect(() => {
|
||||
titleRef.current?.focus({ preventScroll: true });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MobileShell background="#fff7f0">
|
||||
<main
|
||||
className={styles.shell}
|
||||
style={
|
||||
{
|
||||
"--tip-success-cover": `url("${characterCover}")`,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className={styles.coverWash} aria-hidden="true" />
|
||||
<div className={styles.glowOne} aria-hidden="true" />
|
||||
<div className={styles.glowTwo} aria-hidden="true" />
|
||||
|
||||
<section
|
||||
className={styles.card}
|
||||
aria-live="polite"
|
||||
aria-labelledby="tip-success-title"
|
||||
>
|
||||
<div className={styles.visual}>
|
||||
<span className={styles.sparkleOne} aria-hidden="true">
|
||||
<Sparkles size={22} strokeWidth={1.8} />
|
||||
</span>
|
||||
<span className={styles.sparkleTwo} aria-hidden="true">
|
||||
<Heart size={17} fill="currentColor" strokeWidth={1.8} />
|
||||
</span>
|
||||
<div className={styles.avatarFrame}>
|
||||
<CharacterAvatar
|
||||
src={characterAvatar}
|
||||
alt={characterName}
|
||||
size="100%"
|
||||
imageSize={112}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.coffeeFrame}>
|
||||
<Image
|
||||
src={coffeeOption.image.src}
|
||||
alt={`${coffeeOption.displayName} coffee`}
|
||||
width={coffeeOption.image.width}
|
||||
height={coffeeOption.image.height}
|
||||
sizes="78px"
|
||||
className={styles.coffeeImage}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className={styles.eyebrow}>
|
||||
<Coffee size={15} aria-hidden="true" />
|
||||
Coffee received
|
||||
</p>
|
||||
<h1
|
||||
ref={titleRef}
|
||||
id="tip-success-title"
|
||||
className={styles.title}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{copy.title}
|
||||
</h1>
|
||||
{copy.body.length > 0 ? (
|
||||
<div className={styles.copy}>
|
||||
{copy.body.map((paragraph) => (
|
||||
<p key={paragraph}>{paragraph}</p>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.primaryAction}
|
||||
data-analytics-key="tip.send_again"
|
||||
onClick={onSendAgain}
|
||||
>
|
||||
Send another coffee
|
||||
</button>
|
||||
<Link
|
||||
href={splashHref}
|
||||
className={styles.secondaryAction}
|
||||
data-analytics-key="tip.success_back_to_splash"
|
||||
>
|
||||
Back to {characterName}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user