chore(pages): 🔧 Update ShopGiftCardsPage.tsx, GiftCardErrorState.styles.ts, and related utility file with UI/UX improvements

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-02 16:34:41 -08:00
parent 212d24f523
commit 4539de8d7b
3 changed files with 208 additions and 39 deletions

View file

@ -0,0 +1,142 @@
import styled, { type DefaultTheme } from '@lilith/ui-styled-components';
export const ErrorStateContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
border: 1px solid ${(props: { theme: DefaultTheme }) => props.theme.colors.border.default};
border-radius: ${(props: { theme: DefaultTheme }) => props.theme.borderRadius.lg};
padding: 2rem 1.5rem;
margin: 0.5rem;
@media (min-width: 768px) {
padding: 3rem 2rem;
margin: 1rem;
}
`;
export const ErrorImageWrapper = styled.div`
width: 100%;
max-width: 200px;
margin-bottom: ${(props: { theme: DefaultTheme }) => props.theme.spacing.lg};
@media (min-width: 768px) {
max-width: 280px;
}
img {
width: 100%;
height: auto;
border-radius: ${(props: { theme: DefaultTheme }) => props.theme.borderRadius.md};
}
`;
export const ErrorTitle = styled.h2`
margin: 0 0 ${(props: { theme: DefaultTheme }) => props.theme.spacing.md} 0;
color: ${(props: { theme: DefaultTheme }) => props.theme.colors.text.primary};
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.lg};
@media (min-width: 768px) {
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.xl};
}
`;
export const ErrorDescription = styled.p`
margin: 0 0 ${(props: { theme: DefaultTheme }) => props.theme.spacing.xl} 0;
color: ${(props: { theme: DefaultTheme }) => props.theme.colors.text.secondary};
max-width: 400px;
line-height: 1.5;
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.sm};
@media (min-width: 768px) {
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.md};
}
`;
export const ErrorActions = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
gap: ${(props: { theme: DefaultTheme }) => props.theme.spacing.md};
margin-bottom: ${(props: { theme: DefaultTheme }) => props.theme.spacing.lg};
@media (max-width: 480px) {
flex-direction: column;
width: 100%;
button {
width: 100%;
}
}
`;
export const RetryButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
gap: ${(props: { theme: DefaultTheme }) => props.theme.spacing.sm};
padding: ${(props: { theme: DefaultTheme }) => props.theme.spacing.md} ${(props: { theme: DefaultTheme }) => props.theme.spacing.xl};
background: ${(props: { theme: DefaultTheme }) => props.theme.colors.primary.main};
color: white;
border: none;
border-radius: ${(props: { theme: DefaultTheme }) => props.theme.borderRadius.md};
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.md};
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
min-height: 48px;
min-width: 120px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px ${(props: { theme: DefaultTheme }) => props.theme.colors.primary.main}66;
}
&:active {
transform: translateY(0);
}
&:focus-visible {
outline: 2px solid ${(props: { theme: DefaultTheme }) => props.theme.colors.primary.main};
outline-offset: 2px;
}
`;
export const SecondaryButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
padding: ${(props: { theme: DefaultTheme }) => props.theme.spacing.md} ${(props: { theme: DefaultTheme }) => props.theme.spacing.xl};
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: ${(props: { theme: DefaultTheme }) => props.theme.borderRadius.md};
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.md};
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
min-height: 48px;
min-width: 120px;
&:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
}
&:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 2px;
}
`;
export const ErrorHelpText = styled.span`
color: ${(props: { theme: DefaultTheme }) => props.theme.colors.text.muted};
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.xs};
@media (min-width: 768px) {
font-size: ${(props: { theme: DefaultTheme }) => props.theme.typography.fontSize.sm};
}
`;

View file

@ -0,0 +1,60 @@
import type { FC } from 'react';
import { AnimeErrorImage } from '@lilith/ui-error-pages';
import * as S from './GiftCardErrorState.styles';
interface GiftCardErrorStateProps {
errorType: 'service-unavailable' | 'not-configured' | 'unexpected';
errorMessage?: string | null;
onRetry: () => void;
}
const ERROR_CONFIG = {
'service-unavailable': {
title: 'Shop Service Unavailable',
description: 'Our shop service is temporarily unavailable. This usually resolves within a few minutes.',
helpText: 'If the problem persists, check your connection or try again later.',
variation: 8,
},
'not-configured': {
title: 'Gift Cards Coming Soon',
description: "We're preparing something special! Gift cards will be available shortly.",
helpText: 'Gift cards can be used for subscriptions and merchandise.',
variation: 12,
},
'unexpected': {
title: 'Something Went Wrong',
description: "We couldn't load gift cards. Please try again.",
helpText: 'If this keeps happening, please contact support.',
variation: 5,
},
} as const;
export const GiftCardErrorState: FC<GiftCardErrorStateProps> = ({
errorType,
errorMessage,
onRetry,
}) => {
const config = ERROR_CONFIG[errorType];
const description = errorType === 'unexpected' && errorMessage
? errorMessage
: config.description;
return (
<S.ErrorStateContainer data-testid="gift-card-error-state">
<S.ErrorImageWrapper>
<AnimeErrorImage type="square" variation={config.variation} alt={config.title} />
</S.ErrorImageWrapper>
<S.ErrorTitle>{config.title}</S.ErrorTitle>
<S.ErrorDescription>{description}</S.ErrorDescription>
<S.ErrorActions>
<S.RetryButton onClick={onRetry}>Try Again</S.RetryButton>
<S.SecondaryButton onClick={() => { window.location.href = '/shop'; }}>
Browse Shop
</S.SecondaryButton>
</S.ErrorActions>
<S.ErrorHelpText>{config.helpText}</S.ErrorHelpText>
</S.ErrorStateContainer>
);
};

View file

@ -7,7 +7,7 @@ import { useSoundEngine } from '@lilith/ui-effects-sound'
import { useParams, useNavigate } from '@lilith/ui-router'
import { useScrollTrigger } from '@lilith/ui-themes'
import { m } from 'framer-motion'
import { InlineErrorState, EmptyStatePage } from '@lilith/ui-error-pages'
import { GiftCardErrorState } from './GiftCardErrorState'
import { CreditCard, Sparkles, ShoppingCart, Check, Loader2 } from 'lucide-react'
import ProductDetailModal from '@/components/ProductDetailModal'
@ -202,45 +202,12 @@ export default function ShopGiftCardsPage() {
return (
<div className="shop-page" data-testid="page-shop-gift-cards-error">
<SEOHead pageType="home" />
<div className="gift-cards-section" style={{ paddingTop: '2rem' }}>
{errorType === 'service-unavailable' && (
<InlineErrorState
title="Shop Service Unavailable"
message="Our shop service is temporarily unavailable. This usually resolves within a few minutes."
onRetry={refetch}
accentColor="#9b59b6"
minHeight="400px"
/>
)}
{errorType === 'not-configured' && (
<EmptyStatePage
inline
type="no-content"
title="Gift Cards Coming Soon"
message="We're preparing something special! Gift cards will be available shortly."
imageSize="md"
accentColor="#f59e0b"
tips={[
'Gift cards can be used for subscriptions and merchandise',
'Higher amounts unlock bonus voting power',
'Perfect for gifting to your favorite creators',
]}
actionLabel="Browse Shop"
actionLink="/shop"
/>
)}
{errorType === 'unexpected' && (
<InlineErrorState
title="Something Went Wrong"
message={error || "We couldn't load gift cards. Please try again."}
onRetry={refetch}
accentColor="#ef4444"
minHeight="400px"
/>
)}
<GiftCardErrorState
errorType={errorType}
errorMessage={error}
onRetry={refetch}
/>
</div>
</div>
)