190 lines
6.8 KiB
TypeScript
190 lines
6.8 KiB
TypeScript
import {Link} from 'react-router';
|
||
import {Money} from '@shopify/hydrogen';
|
||
import type {CustomerFragment} from 'customer-accountapi.generated';
|
||
import {AccountWishlistStat} from '~/components/AccountWishlistStat';
|
||
import {
|
||
getCustomerDisplayName,
|
||
getCustomerInitials,
|
||
} from '~/lib/customerDisplay';
|
||
|
||
export type AccountOverviewOrder = {
|
||
id: string;
|
||
name: string;
|
||
processedAt: string | null;
|
||
fulfillmentStatus: string | null;
|
||
totalPrice: {
|
||
amount: string;
|
||
currencyCode: string;
|
||
} | null;
|
||
lineItems: {
|
||
nodes: Array<{
|
||
title: string;
|
||
quantity: number;
|
||
price: {
|
||
amount: string;
|
||
currencyCode: string;
|
||
} | null;
|
||
}>;
|
||
} | null;
|
||
};
|
||
|
||
export type AccountOverviewData = {
|
||
creationDate: string | null;
|
||
orders: {
|
||
pageInfo: {hasNextPage: boolean};
|
||
nodes: AccountOverviewOrder[];
|
||
} | null;
|
||
};
|
||
|
||
function sumTotalSpent(orders: AccountOverviewOrder[], currency: string) {
|
||
let total = 0;
|
||
for (const o of orders) {
|
||
if (o.totalPrice && o.totalPrice.currencyCode === currency) {
|
||
total += Number.parseFloat(o.totalPrice.amount) || 0;
|
||
}
|
||
}
|
||
return total;
|
||
}
|
||
|
||
type Props = {
|
||
customer: CustomerFragment;
|
||
overview: AccountOverviewData;
|
||
};
|
||
|
||
/**
|
||
* Figma 123:11834 account overview: profile band, stat tiles, recent order.
|
||
*/
|
||
export function AccountOverview({customer, overview}: Props) {
|
||
const orders = overview.orders?.nodes ?? [];
|
||
const hasNext = overview.orders?.pageInfo.hasNextPage ?? false;
|
||
const count = orders.length;
|
||
const orderCountLabel = hasNext ? `${count}+` : String(count);
|
||
|
||
const spendCurrency = orders.find((o) => o.totalPrice)?.totalPrice
|
||
?.currencyCode;
|
||
|
||
const recent = orders[0] ?? null;
|
||
|
||
const memberLabel =
|
||
overview.creationDate ?
|
||
`Member since ${new Date(overview.creationDate).getFullYear()}`
|
||
: 'Member';
|
||
|
||
return (
|
||
<div className="flex flex-col gap-6">
|
||
<div className="flex flex-wrap items-center gap-4 rounded-sm bg-[#5d0b24] px-5 py-5 text-white">
|
||
<div
|
||
aria-hidden
|
||
className="flex h-16 w-16 shrink-0 items-center justify-center rounded-full border border-white/30 bg-white/10 font-['Raleway',sans-serif] text-lg font-medium uppercase tracking-wide"
|
||
>
|
||
{getCustomerInitials(customer)}
|
||
</div>
|
||
<div className="min-w-0">
|
||
<p className="font-['Raleway',sans-serif] text-base font-medium tracking-tight sm:text-lg">
|
||
{getCustomerDisplayName(customer) || 'Account'}
|
||
</p>
|
||
<p className="mt-0.5 font-['Raleway',sans-serif] text-sm text-white/85">
|
||
{memberLabel}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<ul className="grid grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4">
|
||
<AccountWishlistStat />
|
||
<li className="border border-[rgba(26,26,26,0.1)] bg-[#faf8f3] px-4 py-4 text-center sm:text-left">
|
||
<p className="font-['Raleway',sans-serif] text-xs uppercase tracking-[0.12em] text-[#5a5550]">
|
||
Orders
|
||
</p>
|
||
<p className="mt-1 font-['Raleway',sans-serif] text-xl font-medium text-[#1a1a18]">
|
||
{orderCountLabel}
|
||
</p>
|
||
</li>
|
||
<li className="border border-[rgba(26,26,26,0.1)] bg-[#faf8f3] px-4 py-4 text-center sm:text-left">
|
||
<p className="font-['Raleway',sans-serif] text-xs uppercase tracking-[0.12em] text-[#5a5550]">
|
||
Total spent
|
||
</p>
|
||
<div className="mt-1 font-['Raleway',sans-serif] text-xl font-medium text-[#1a1a18]">
|
||
{orders.length > 0 && spendCurrency ?
|
||
<Money
|
||
data={{
|
||
amount: String(sumTotalSpent(orders, spendCurrency)),
|
||
currencyCode: spendCurrency,
|
||
}}
|
||
/>
|
||
: '—'}
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
|
||
<section className="border border-[rgba(26,26,26,0.1)] bg-white" aria-label="Recent order">
|
||
<div className=" px-4 pt-2 pb-3">
|
||
<h2 className="m-0 font-['Cormorant_Garamond',serif] text-[29.75px] font-light not-italic leading-[33.32px] tracking-[-0.446px] text-[#1A1A1A]">
|
||
Recent order
|
||
</h2>
|
||
</div>
|
||
{recent ?
|
||
<div className="px-4 py-4">
|
||
<div className="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
|
||
<p className="font-['Raleway',sans-serif] text-base font-medium text-[#1a1a18]">
|
||
{recent.name}
|
||
</p>
|
||
<p className="font-['Raleway',sans-serif] text-sm text-[#5a5550]">
|
||
{recent.processedAt ?
|
||
new Date(recent.processedAt).toLocaleDateString()
|
||
: '—'}
|
||
</p>
|
||
</div>
|
||
{recent.fulfillmentStatus ?
|
||
<p className="mt-1 font-['Raleway',sans-serif] text-sm text-[#5a5550]">
|
||
{recent.fulfillmentStatus}
|
||
</p>
|
||
: null}
|
||
{recent.lineItems?.nodes.length ?
|
||
<ul className="mt-3 space-y-1 border-t border-[rgba(26,26,26,0.06)] pt-3">
|
||
{recent.lineItems.nodes.map((line, i) => (
|
||
<li
|
||
className="flex flex-wrap items-baseline justify-between gap-2 text-sm"
|
||
key={`${line.title}-${i}`}
|
||
>
|
||
<span className="font-['Raleway',sans-serif] text-[#1a1a18]">
|
||
{line.title}{' '}
|
||
<span className="text-[#5a5550]">×{line.quantity}</span>
|
||
</span>
|
||
{line.price ?
|
||
<span className="font-['Raleway',sans-serif] text-[#1a1a18]">
|
||
<Money data={line.price} />
|
||
</span>
|
||
: null}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
: null}
|
||
{recent.totalPrice ?
|
||
<div className="mt-3 flex justify-end border-t border-[rgba(26,26,26,0.06)] pt-3 font-['Raleway',sans-serif] text-sm text-[#1a1a18]">
|
||
Total:{' '}
|
||
<span className="ml-1">
|
||
<Money data={recent.totalPrice} />
|
||
</span>
|
||
</div>
|
||
: null}
|
||
<p className="mt-4">
|
||
<Link
|
||
className="font-['Raleway',sans-serif] text-sm font-medium text-[#5d0b24] no-underline hover:opacity-80"
|
||
to={`/account/orders/${btoa(recent.id)}`}
|
||
>
|
||
View order
|
||
</Link>
|
||
</p>
|
||
</div>
|
||
: <p className="px-4 pt-10 pb-8 text-center font-['Raleway',sans-serif] text-sm text-[#5a5550]">
|
||
You haven’t placed an order yet.{' '}
|
||
<Link className="text-[#5d0b24] no-underline hover:opacity-80" to="/collections/all">
|
||
Browse the shop
|
||
</Link>
|
||
</p>
|
||
}
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|