Finish admin page
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
|
||||
import type { Locale } from '@sport/types';
|
||||
import { SkipLink } from '@sport/ui';
|
||||
|
||||
import { SiteHeader } from '@/components/commerce/site-header';
|
||||
import { SiteFooter } from '@/components/layout/site-footer';
|
||||
@@ -24,12 +25,17 @@ export default async function AccountGroupLayout({
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
const t = await getTranslations('common');
|
||||
|
||||
const navigation = await fetchNavigation(locale as Locale);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<SkipLink>{t('skipToContent')}</SkipLink>
|
||||
<SiteHeader navigation={navigation} locale={locale as Locale} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<main id="main" className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<SiteFooter navigation={navigation} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default async function CheckoutLayout({
|
||||
<Link href={routes.home()} className="text-lg font-black uppercase tracking-tighter">
|
||||
Sport<span className="text-volt-600">.</span>
|
||||
</Link>
|
||||
<span className="text-ink-400 text-xs font-semibold uppercase tracking-widest">
|
||||
<span className="text-ink-500 text-xs font-semibold uppercase tracking-widest">
|
||||
{t('title')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default async function PostPage({ params }: PageProps) {
|
||||
{post.publishedAt ? (
|
||||
<time
|
||||
dateTime={post.publishedAt}
|
||||
className="text-ink-400 text-xs uppercase tracking-widest"
|
||||
className="text-ink-500 text-xs uppercase tracking-widest"
|
||||
>
|
||||
{format.dateTime(new Date(post.publishedAt), { dateStyle: 'long' })}
|
||||
</time>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default async function BlogPage({ params }: PageProps) {
|
||||
{post.publishedAt ? (
|
||||
<time
|
||||
dateTime={post.publishedAt}
|
||||
className="text-ink-400 mt-4 block text-xs uppercase tracking-widest"
|
||||
className="text-ink-500 mt-4 block text-xs uppercase tracking-widest"
|
||||
>
|
||||
{format.dateTime(new Date(post.publishedAt), { dateStyle: 'medium' })}
|
||||
</time>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
|
||||
import type { Locale } from '@sport/types';
|
||||
import { SkipLink } from '@sport/ui';
|
||||
|
||||
import { SiteHeader } from '@/components/commerce/site-header';
|
||||
import { SiteFooter } from '@/components/layout/site-footer';
|
||||
@@ -27,12 +28,17 @@ export default async function ShopLayout({
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
|
||||
const t = await getTranslations('common');
|
||||
|
||||
const navigation = await fetchNavigation(locale as Locale);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<SkipLink>{t('skipToContent')}</SkipLink>
|
||||
<SiteHeader navigation={navigation} locale={locale as Locale} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<main id="main" className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<SiteFooter navigation={navigation} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function HomePage({ params }: PageProps) {
|
||||
/>
|
||||
|
||||
<section className="max-w-page px-gutter mx-auto py-16">
|
||||
<h2 className="text-ink-400 text-xs font-semibold uppercase tracking-widest">
|
||||
<h2 className="text-ink-500 text-xs font-semibold uppercase tracking-widest">
|
||||
{t('shopBySport')}
|
||||
</h2>
|
||||
<ul className="mt-6 grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function NotFound() {
|
||||
|
||||
return (
|
||||
<div className="max-w-page px-gutter mx-auto flex min-h-screen flex-col items-center justify-center text-center">
|
||||
<p className="text-ink-400 text-xs font-semibold uppercase tracking-widest">{t('code')}</p>
|
||||
<p className="text-ink-500 text-xs font-semibold uppercase tracking-widest">{t('code')}</p>
|
||||
<h1 className="mt-4 text-5xl font-black uppercase">{t('title')}</h1>
|
||||
<p className="text-ink-500 mt-4 max-w-md">{t('body')}</p>
|
||||
<Button className="mt-8" asChild>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function CartBadge() {
|
||||
>
|
||||
<ShoppingBag className="size-5" />
|
||||
{!loading && count > 0 ? (
|
||||
<span className="bg-volt-500 text-ink-950 absolute right-0.5 top-0.5 grid min-w-4 place-items-center rounded-full px-1 text-[0.625rem] font-bold leading-4">
|
||||
<span className="bg-volt-500 text-ink-950 text-eyebrow absolute right-0.5 top-0.5 grid min-w-4 place-items-center rounded-full px-1 font-bold leading-4">
|
||||
{count}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function CartView() {
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="text-ink-500 mt-1 text-xs">{line.variantTitle}</p>
|
||||
<p className="text-ink-400 mt-0.5 font-mono text-[0.625rem]">{line.sku}</p>
|
||||
<p className="text-ink-500 text-eyebrow mt-0.5 font-mono">{line.sku}</p>
|
||||
|
||||
<div className="mt-3 flex items-center gap-4">
|
||||
<QuantityStepper
|
||||
@@ -131,7 +131,7 @@ export function CartView() {
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-semibold">{formatMoney(line.lineTotal, format)}</p>
|
||||
{line.compareAtPrice ? (
|
||||
<p className="text-ink-400 text-xs line-through">
|
||||
<p className="text-ink-500 text-xs line-through">
|
||||
{formatMoney(
|
||||
{
|
||||
amount: line.compareAtPrice.amount * line.quantity,
|
||||
@@ -141,7 +141,7 @@ export function CartView() {
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="text-ink-400 mt-1 text-xs">
|
||||
<p className="text-ink-500 mt-1 text-xs">
|
||||
{formatMoney(line.unitPrice, format)} × {line.quantity}
|
||||
</p>
|
||||
</div>
|
||||
@@ -189,7 +189,7 @@ function Row({ label, value, muted }: { label: string; value: string; muted?: bo
|
||||
return (
|
||||
<div className="flex items-baseline justify-between">
|
||||
<dt className="text-ink-500">{label}</dt>
|
||||
<dd className={muted ? 'text-ink-400 text-xs' : ''}>{value}</dd>
|
||||
<dd className={muted ? 'text-ink-500 text-xs' : ''}>{value}</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export function CheckoutForm({ locale }: { locale: Locale }) {
|
||||
) : null}
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-ink-500">{t('shipping')}</dt>
|
||||
<dd className="text-ink-400 text-xs">{t('shippingLater')}</dd>
|
||||
<dd className="text-ink-500 text-xs">{t('shippingLater')}</dd>
|
||||
</div>
|
||||
<div className="border-ink-200 flex items-baseline justify-between border-t pt-2">
|
||||
<dt className="font-semibold">{t('total')}</dt>
|
||||
@@ -315,7 +315,7 @@ export function CheckoutForm({ locale }: { locale: Locale }) {
|
||||
</Button>
|
||||
|
||||
{/* Honest about scope: payment providers arrive with M9. */}
|
||||
<p className="text-ink-400 mt-3 text-center text-xs">{t('paymentLater')}</p>
|
||||
<p className="text-ink-500 mt-3 text-center text-xs">{t('paymentLater')}</p>
|
||||
</aside>
|
||||
</form>
|
||||
);
|
||||
@@ -339,7 +339,7 @@ function Field({
|
||||
{required ? <span className="text-danger ml-1">*</span> : null}
|
||||
</span>
|
||||
{children}
|
||||
{hint ? <span className="text-ink-400 block text-xs">{hint}</span> : null}
|
||||
{hint ? <span className="text-ink-500 block text-xs">{hint}</span> : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export function DiscountForm({
|
||||
disabled={pending}
|
||||
onClick={() => void removeCode(discount.code as string)}
|
||||
aria-label={t('removeCode', { code: discount.code })}
|
||||
className="text-ink-400 hover:text-danger focus-visible:ring-ring/50 outline-none focus-visible:ring-[3px] disabled:opacity-50"
|
||||
className="text-ink-500 hover:text-danger focus-visible:ring-ring/50 outline-none focus-visible:ring-[3px] disabled:opacity-50"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function FilterSheet({
|
||||
<SlidersHorizontal />
|
||||
{t('filters')}
|
||||
{activeCount > 0 ? (
|
||||
<span className="bg-ink-950 grid size-5 place-items-center rounded-full text-[0.625rem] text-white">
|
||||
<span className="bg-ink-950 text-eyebrow grid size-5 place-items-center rounded-full text-white">
|
||||
{activeCount}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function LoadMore({
|
||||
) : null}
|
||||
|
||||
<div className="mt-12 flex flex-col items-center gap-3">
|
||||
<p className="text-ink-400 text-xs uppercase tracking-widest">
|
||||
<p className="text-ink-500 text-xs uppercase tracking-widest">
|
||||
{t('showing', { shown, total: totalCount })}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -103,9 +103,7 @@ export function MegaMenu({
|
||||
<div className="max-w-page px-gutter mx-auto grid grid-cols-2 gap-10 py-10 md:grid-cols-4">
|
||||
{entry.columns.map((column) => (
|
||||
<div key={column.heading}>
|
||||
<p className="text-volt-500 text-[0.625rem] font-semibold uppercase tracking-widest">
|
||||
{column.heading}
|
||||
</p>
|
||||
<p className="text-volt-500 text-eyebrow uppercase">{column.heading}</p>
|
||||
<ul className="mt-4 space-y-2.5">
|
||||
{column.links.map((link) => (
|
||||
<li key={link.href}>
|
||||
|
||||
@@ -84,9 +84,7 @@ export function ProductCard({
|
||||
|
||||
<div className="mt-3 space-y-1">
|
||||
{product.brandName ? (
|
||||
<p className="text-ink-400 text-[0.625rem] font-semibold uppercase tracking-widest">
|
||||
{product.brandName}
|
||||
</p>
|
||||
<p className="text-ink-500 text-eyebrow uppercase">{product.brandName}</p>
|
||||
) : null}
|
||||
|
||||
<h3 className="text-ink-950 text-sm font-medium">
|
||||
@@ -107,7 +105,7 @@ export function ProductCard({
|
||||
{formatMoney(priceRange.min, format)}
|
||||
</span>
|
||||
{priceRange.compareAtMax && product.isOnSale ? (
|
||||
<span className="text-ink-400 text-xs line-through">
|
||||
<span className="text-ink-500 text-xs line-through">
|
||||
{formatMoney(priceRange.compareAtMax, format)}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -125,7 +123,7 @@ export function ProductCard({
|
||||
/>
|
||||
))}
|
||||
{product.colorSwatches.length > 5 ? (
|
||||
<li className="text-ink-400 text-[0.625rem]">+{product.colorSwatches.length - 5}</li>
|
||||
<li className="text-ink-500 text-eyebrow">+{product.colorSwatches.length - 5}</li>
|
||||
) : null}
|
||||
</ul>
|
||||
) : null}
|
||||
|
||||
@@ -118,7 +118,7 @@ export function ProductDetail({ product }: { product: StorefrontProduct }) {
|
||||
<div className="space-y-8 lg:pt-4">
|
||||
<div>
|
||||
{product.brand ? (
|
||||
<p className="text-ink-400 text-xs font-semibold uppercase tracking-widest">
|
||||
<p className="text-ink-500 text-xs font-semibold uppercase tracking-widest">
|
||||
{product.brand.name}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -134,7 +134,7 @@ export function ProductDetail({ product }: { product: StorefrontProduct }) {
|
||||
</span>
|
||||
{compareAt && discount > 0 ? (
|
||||
<>
|
||||
<span className="text-ink-400 text-base line-through">
|
||||
<span className="text-ink-500 text-base line-through">
|
||||
{formatMoney(compareAt, format)}
|
||||
</span>
|
||||
<span className="text-sale text-xs font-semibold uppercase tracking-widest">
|
||||
@@ -182,7 +182,7 @@ export function ProductDetail({ product }: { product: StorefrontProduct }) {
|
||||
// Reachable but sold out reads differently from
|
||||
// impossible: struck through, still selectable, so
|
||||
// stock is discoverable rather than hidden.
|
||||
reachableValue && !stocked && 'text-ink-400 line-through',
|
||||
reachableValue && !stocked && 'text-ink-500 line-through',
|
||||
)}
|
||||
style={
|
||||
isColour && value.swatchHex
|
||||
@@ -207,7 +207,7 @@ export function ProductDetail({ product }: { product: StorefrontProduct }) {
|
||||
<WishlistButton productId={product.id} productSlug={product.slug} />
|
||||
|
||||
{selectedVariant ? (
|
||||
<p className="text-ink-400 text-center text-xs">
|
||||
<p className="text-ink-500 text-center text-xs">
|
||||
{t('sku')}: {selectedVariant.sku}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -144,7 +144,7 @@ export async function ProductFilters({
|
||||
)}
|
||||
>
|
||||
<span>{brand.label}</span>
|
||||
<span className="text-ink-400 text-xs">{brand.count}</span>
|
||||
<span className="text-ink-500 text-xs">{brand.count}</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function ProductListing({
|
||||
<div className="max-w-page px-gutter mx-auto py-12">
|
||||
<header className="mb-10">
|
||||
{eyebrow ? (
|
||||
<p className="text-ink-400 text-xs font-semibold uppercase tracking-widest">{eyebrow}</p>
|
||||
<p className="text-ink-500 text-xs font-semibold uppercase tracking-widest">{eyebrow}</p>
|
||||
) : null}
|
||||
<h1 className="mt-2 text-4xl font-black uppercase sm:text-5xl">{title}</h1>
|
||||
{description ? <p className="text-ink-500 mt-4 max-w-2xl text-sm">{description}</p> : null}
|
||||
@@ -71,7 +71,7 @@ export async function ProductListing({
|
||||
instead of pushing the products another screen down.
|
||||
*/}
|
||||
<div className="border-ink-200 mb-6 flex items-center justify-between gap-4 border-b pb-4">
|
||||
<p className="text-ink-400 text-xs uppercase tracking-widest">
|
||||
<p className="text-ink-500 text-xs uppercase tracking-widest">
|
||||
{t('resultsCount', { count: result.totalCount })}
|
||||
</p>
|
||||
<SortMenu basePath={basePath} query={query} />
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Star } from 'lucide-react';
|
||||
import { getFormatter, getTranslations } from 'next-intl/server';
|
||||
|
||||
import type { ProductReviews as ProductReviewsPayload } from '@sport/api-client';
|
||||
import { cn } from '@sport/ui';
|
||||
import { Rating } from '@sport/ui';
|
||||
|
||||
import { getServerApi } from '@/lib/api';
|
||||
|
||||
@@ -50,9 +49,9 @@ export async function ProductReviews({ productId }: { productId: string }) {
|
||||
<span className="text-5xl font-black tabular-nums">
|
||||
{format.number(summary.average, { minimumFractionDigits: 1 })}
|
||||
</span>
|
||||
<span className="text-ink-400 text-sm">/ 5</span>
|
||||
<span className="text-ink-500 text-sm">/ 5</span>
|
||||
</div>
|
||||
<Stars rating={Math.round(summary.average)} className="mt-2" />
|
||||
<Rating value={Math.round(summary.average)} className="mt-2" />
|
||||
<p className="text-ink-500 mt-2 text-xs">{t('count', { count: summary.count })}</p>
|
||||
</div>
|
||||
|
||||
@@ -91,7 +90,7 @@ export async function ProductReviews({ productId }: { productId: string }) {
|
||||
{items.map((review) => (
|
||||
<li key={review.id} className="py-6">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Stars rating={review.rating} />
|
||||
<Rating value={review.rating} />
|
||||
{review.title ? <h3 className="text-sm font-semibold">{review.title}</h3> : null}
|
||||
</div>
|
||||
|
||||
@@ -126,17 +125,3 @@ export async function ProductReviews({ productId }: { productId: string }) {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function Stars({ rating, className }: { rating: number; className?: string }) {
|
||||
return (
|
||||
<span className={cn('flex items-center gap-0.5', className)} aria-label={`${rating}/5`}>
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<Star
|
||||
key={star}
|
||||
aria-hidden
|
||||
className={cn('size-4', star <= rating ? 'fill-ink-950 text-ink-950' : 'text-ink-300')}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SearchDialog({ locale }: { locale: Locale }) {
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={submit} className="border-ink-200 flex items-center gap-3 border-b px-5">
|
||||
<Search className="text-ink-400 size-5 shrink-0" />
|
||||
<Search className="text-ink-500 size-5 shrink-0" />
|
||||
{/* No `autoFocus`: Radix moves focus to the first focusable element
|
||||
when the dialog opens, which is this input. Setting it as well
|
||||
fights the dialog's own focus management. */}
|
||||
@@ -126,9 +126,9 @@ export function SearchDialog({ locale }: { locale: Locale }) {
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={t('placeholder')}
|
||||
aria-label={t('title')}
|
||||
className="placeholder:text-ink-400 h-14 w-full bg-transparent text-base outline-none"
|
||||
className="placeholder:text-ink-500 h-14 w-full bg-transparent text-base outline-none"
|
||||
/>
|
||||
{loading ? <Loader2 className="text-ink-400 size-4 animate-spin" /> : null}
|
||||
{loading ? <Loader2 className="text-ink-500 size-4 animate-spin" /> : null}
|
||||
</form>
|
||||
|
||||
{active && suggestions.length > 0 ? (
|
||||
@@ -151,7 +151,7 @@ export function SearchDialog({ locale }: { locale: Locale }) {
|
||||
<p className="text-ink-500 px-5 py-6 text-center text-sm">{t('noSuggestions')}</p>
|
||||
) : null}
|
||||
|
||||
<p className="border-ink-200 text-ink-400 border-t px-5 py-3 text-xs">
|
||||
<p className="border-ink-200 text-ink-500 border-t px-5 py-3 text-xs">
|
||||
{t('enterToSearch')}
|
||||
</p>
|
||||
</DialogContent>
|
||||
|
||||
@@ -35,7 +35,7 @@ export function SortMenu({ basePath, query }: { basePath: string; query: Product
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<span className="text-ink-400 hidden font-normal normal-case sm:inline">
|
||||
<span className="text-ink-500 hidden font-normal normal-case sm:inline">
|
||||
{t('sortBy')}
|
||||
</span>
|
||||
{t(`sort.${active}`)}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
'px-1.5 py-1 text-xs font-semibold uppercase tracking-widest transition-colors',
|
||||
locale === activeLocale
|
||||
? 'text-ink-950 underline underline-offset-4'
|
||||
: 'text-ink-400 hover:text-ink-950',
|
||||
: 'text-ink-500 hover:text-ink-950',
|
||||
)}
|
||||
>
|
||||
{locale}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function PageScaffold({
|
||||
return (
|
||||
<div className="max-w-page px-gutter mx-auto py-20">
|
||||
{eyebrow ? (
|
||||
<p className="text-ink-400 text-xs font-semibold uppercase tracking-widest">{eyebrow}</p>
|
||||
<p className="text-ink-500 text-xs font-semibold uppercase tracking-widest">{eyebrow}</p>
|
||||
) : null}
|
||||
|
||||
<h1 className="mt-3 text-4xl font-black uppercase sm:text-6xl">{title}</h1>
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
OrderListItem,
|
||||
ProductListItem,
|
||||
} from '@sport/types';
|
||||
import { Badge, Button, Input, Skeleton } from '@sport/ui';
|
||||
import { Badge, Button, EmptyState, Input, Skeleton } from '@sport/ui';
|
||||
|
||||
import { Link } from '@/i18n/navigation';
|
||||
import { browserApi } from '@/lib/api';
|
||||
@@ -124,7 +124,7 @@ export function AccountOrders() {
|
||||
{!data ? (
|
||||
<Skeleton className="h-40 w-full" />
|
||||
) : data.items.length === 0 ? (
|
||||
<p className="text-ink-500 text-sm">{t('noOrders')}</p>
|
||||
<EmptyState title={t('noOrders')} />
|
||||
) : (
|
||||
<ul className="divide-ink-100 divide-y border-y">
|
||||
{data.items.map((order) => (
|
||||
@@ -329,7 +329,7 @@ export function AccountAddresses() {
|
||||
{!data ? (
|
||||
<Skeleton className="h-32 w-full" />
|
||||
) : data.length === 0 && !draft ? (
|
||||
<p className="text-ink-500 text-sm">{t('noAddresses')}</p>
|
||||
<EmptyState title={t('noAddresses')} />
|
||||
) : (
|
||||
<ul className="grid gap-4 sm:grid-cols-2">
|
||||
{data.map((address) => (
|
||||
@@ -454,7 +454,7 @@ export function AccountProfile() {
|
||||
flow. The API refuses it too — this input simply says so.
|
||||
*/}
|
||||
<Input value={data.email} readOnly disabled />
|
||||
<span className="text-ink-400 text-xs">{t('emailLocked')}</span>
|
||||
<span className="text-ink-500 text-xs">{t('emailLocked')}</span>
|
||||
</label>
|
||||
|
||||
<Field label={t('phone')} value={data.phone ?? ''} onChange={(v) => patch({ phone: v })} />
|
||||
@@ -526,7 +526,7 @@ export function AccountWishlist() {
|
||||
{!data ? (
|
||||
<Skeleton className="h-32 w-full" />
|
||||
) : data.length === 0 ? (
|
||||
<p className="text-ink-500 text-sm">{t('noWishlist')}</p>
|
||||
<EmptyState title={t('noWishlist')} />
|
||||
) : (
|
||||
<ul className="divide-ink-100 divide-y border-y">
|
||||
{data.map((product) => (
|
||||
|
||||
@@ -133,14 +133,14 @@ export function AuthForm({ mode }: { mode: 'signIn' | 'register' }) {
|
||||
autoComplete={isRegister ? 'new-password' : 'current-password'}
|
||||
required
|
||||
/>
|
||||
{isRegister ? <span className="text-ink-400 text-xs">{t('passwordHint')}</span> : null}
|
||||
{isRegister ? <span className="text-ink-500 text-xs">{t('passwordHint')}</span> : null}
|
||||
</label>
|
||||
|
||||
{isRegister ? (
|
||||
<>
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-semibold uppercase tracking-widest">
|
||||
{t('phone')} <span className="text-ink-400 normal-case">{t('optional')}</span>
|
||||
{t('phone')} <span className="text-ink-500 normal-case">{t('optional')}</span>
|
||||
</span>
|
||||
<Input
|
||||
type="tel"
|
||||
|
||||
@@ -308,5 +308,8 @@
|
||||
"failed": "That did not work. Check your details and try again.",
|
||||
"noAccount": "No account yet?",
|
||||
"haveAccount": "Already have an account?"
|
||||
},
|
||||
"common": {
|
||||
"skipToContent": "Skip to content"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,5 +308,8 @@
|
||||
"failed": "Chưa được. Kiểm tra lại thông tin và thử lần nữa.",
|
||||
"noAccount": "Chưa có tài khoản?",
|
||||
"haveAccount": "Đã có tài khoản?"
|
||||
},
|
||||
"common": {
|
||||
"skipToContent": "Bỏ qua, tới nội dung chính"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user