diff --git a/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.styles.ts b/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.styles.ts new file mode 100644 index 000000000..725c7ea7d --- /dev/null +++ b/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.styles.ts @@ -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}; + } +`; diff --git a/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.tsx b/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.tsx new file mode 100644 index 000000000..87b9e8076 --- /dev/null +++ b/features/landing/frontend-public/src/pages/shop/GiftCardErrorState.tsx @@ -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 = ({ + errorType, + errorMessage, + onRetry, +}) => { + const config = ERROR_CONFIG[errorType]; + const description = errorType === 'unexpected' && errorMessage + ? errorMessage + : config.description; + + return ( + + + + + {config.title} + {description} + + Try Again + { window.location.href = '/shop'; }}> + Browse Shop + + + {config.helpText} + + ); +}; diff --git a/features/landing/frontend-public/src/pages/shop/ShopGiftCardsPage.tsx b/features/landing/frontend-public/src/pages/shop/ShopGiftCardsPage.tsx index 545514cef..45091d833 100755 --- a/features/landing/frontend-public/src/pages/shop/ShopGiftCardsPage.tsx +++ b/features/landing/frontend-public/src/pages/shop/ShopGiftCardsPage.tsx @@ -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 (
-
- {errorType === 'service-unavailable' && ( - - )} - - {errorType === 'not-configured' && ( - - )} - - {errorType === 'unexpected' && ( - - )} +
)