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:
parent
212d24f523
commit
4539de8d7b
3 changed files with 208 additions and 39 deletions
|
|
@ -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};
|
||||
}
|
||||
`;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue