Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser
- 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>
This commit is contained in:
commit
cec967f953
321 changed files with 80506 additions and 0 deletions
116
web/src/components/Settings/Subscription/SubscriptionCard.tsx
Normal file
116
web/src/components/Settings/Subscription/SubscriptionCard.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/** @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>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue