- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3) - Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor - Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS - Configurare adresă platformă externalizată (build args / .env / config.js) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
/** @jsxImportSource @emotion/react */
|
|
import { css } from '@emotion/react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import type { UsageStats } from '../../../services/subscription.service';
|
|
|
|
interface SubscriptionCardProps {
|
|
data: UsageStats;
|
|
onUpgrade: () => void;
|
|
}
|
|
|
|
const cardStyles = css`
|
|
background: var(--bg-surface); border: 1px solid var(--accent-border);
|
|
border-radius: 12px; padding: 24px; color: var(--fg-primary);
|
|
transition: all 0.3s ease;
|
|
box-sizing: border-box; min-width: 0; overflow: hidden;
|
|
&:hover { border-color: var(--accent); background: var(--bg-hover); }
|
|
|
|
@media (max-width: 480px) {
|
|
padding: 16px;
|
|
}
|
|
`;
|
|
|
|
const headerStyles = css`
|
|
display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; gap: 12px;
|
|
flex-wrap: wrap;
|
|
`;
|
|
|
|
const planNameStyles = css`
|
|
font-size: 28px; font-weight: bold; margin: 0 0 8px 0; color: var(--fg-primary);
|
|
word-break: break-word;
|
|
|
|
@media (max-width: 480px) {
|
|
font-size: 22px;
|
|
}
|
|
`;
|
|
|
|
const priceStyles = css`
|
|
font-size: 16px; color: var(--fg-secondary);
|
|
`;
|
|
|
|
const badgeStyles = css`
|
|
background: var(--accent-subtle); border: 1px solid var(--accent-border);
|
|
padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;
|
|
text-transform: uppercase; letter-spacing: 0.5px; color: var(--fg-primary);
|
|
`;
|
|
|
|
const featureListStyles = css`list-style: none; padding: 0; margin: 20px 0;`;
|
|
|
|
const featureItemStyles = css`
|
|
display: flex; align-items: center; margin-bottom: 12px; font-size: 14px; color: var(--fg-primary);
|
|
&::before { content: '✓'; display: inline-block; width: 20px; height: 20px;
|
|
background: var(--accent); border-radius: 50%; margin-right: 12px;
|
|
text-align: center; line-height: 20px; font-weight: bold; color: var(--fg-on-accent); }
|
|
`;
|
|
|
|
const upgradeButtonStyles = css`
|
|
margin-top: 20px; padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 600;
|
|
cursor: pointer; transition: all 0.3s ease;
|
|
background: var(--accent-subtle); border: 2px solid var(--accent); color: var(--fg-primary);
|
|
&:hover { transform: translateY(-2px); background: var(--accent-subtle);
|
|
border-color: var(--accent-hover); box-shadow: var(--shadow-md); }
|
|
`;
|
|
|
|
const periodInfoStyles = css`
|
|
font-size: 13px; color: var(--fg-secondary); margin-top: 16px; padding-top: 16px;
|
|
border-top: 1px solid var(--accent-border); word-break: break-word;
|
|
`;
|
|
|
|
export const SubscriptionCard = ({ data, onUpgrade }: SubscriptionCardProps) => {
|
|
const { t } = useTranslation();
|
|
const { plan, subscription, credits } = data;
|
|
const isFree = plan.priceAmount === 0;
|
|
|
|
const features = [
|
|
t('subscription.creditsPerCycleFeature', { count: plan.creditsPerCycle }),
|
|
t('subscription.storageGb', { count: plan.storageLimitGb }),
|
|
];
|
|
|
|
return (
|
|
<div css={cardStyles}>
|
|
<div css={headerStyles}>
|
|
<div>
|
|
<h2 css={planNameStyles}>{plan.name}</h2>
|
|
{plan.priceAmount > 0 && (
|
|
<div css={priceStyles}>${plan.priceAmount}/mo</div>
|
|
)}
|
|
</div>
|
|
<div css={badgeStyles}>
|
|
{subscription.isActive ? t('common.active') : t('common.inactive')}
|
|
</div>
|
|
</div>
|
|
|
|
<ul css={featureListStyles}>
|
|
{features.map((feature, i) => (
|
|
<li key={i} css={featureItemStyles}>{feature}</li>
|
|
))}
|
|
</ul>
|
|
|
|
{isFree && (
|
|
<button css={upgradeButtonStyles} onClick={onUpgrade}>
|
|
{t('subscription.upgradePlan')}
|
|
</button>
|
|
)}
|
|
|
|
<div css={periodInfoStyles}>
|
|
{isFree
|
|
? t('subscription.freePlanInfo')
|
|
: subscription.activationDate
|
|
? t('subscription.activeSince', { date: new Date(subscription.activationDate).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) })
|
|
: t('subscription.activeSubscription')
|
|
}
|
|
{credits.remaining > 0 && ` — ${t('subscription.creditsRemaining', { count: credits.remaining })}`}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|