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:
Top Clossers 2026-07-17 12:33:59 +03:00
commit cec967f953
321 changed files with 80506 additions and 0 deletions

View file

@ -0,0 +1,91 @@
import React from 'react';
import styled from '@emotion/styled';
import { colors, typography, spacing, borderRadius, animation } from '../../theme';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
}
export const Input: React.FC<InputProps> = ({
label,
error,
helperText,
id,
...props
}) => {
const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
return (
<Container>
{label && <Label htmlFor={inputId}>{label}</Label>}
<StyledInput id={inputId} hasError={!!error} {...props} />
{error && <ErrorText>{error}</ErrorText>}
{helperText && !error && <HelperText>{helperText}</HelperText>}
</Container>
);
};
const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const Label = styled.label`
display: block;
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.medium};
color: var(--fg-secondary);
margin-bottom: ${spacing.sm}px;
text-transform: uppercase;
letter-spacing: 0.5px;
`;
const StyledInput = styled.input<{ hasError: boolean }>`
width: 100%;
padding: ${spacing.md}px ${spacing.lg}px;
background: var(--bg-surface);
border: 1px solid ${props => props.hasError ? colors.cautionRed : 'var(--border-default)'};
border-radius: 10px;
color: var(--fg-primary);
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.base};
transition: all ${animation.duration.normal} ${animation.easing.default};
&::placeholder {
color: var(--fg-muted);
}
&:focus {
outline: none;
border-color: ${props => props.hasError ? colors.cautionRed : 'var(--accent)'};
background: ${props => props.hasError ? 'rgba(230, 57, 70, 0.05)' : 'var(--bg-surface)'};
box-shadow: 0 0 0 3px ${props => props.hasError ? 'rgba(230, 57, 70, 0.1)' : 'rgba(0, 145, 152, 0.12)'};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--bg-hover);
}
`;
const ErrorText = styled.span`
display: block;
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.sm};
color: ${colors.cautionRed};
margin-top: ${spacing.xs}px;
`;
const HelperText = styled.span`
display: block;
font-family: ${typography.fontFamily.primary};
font-size: ${typography.fontSize.sm};
color: var(--fg-muted);
margin-top: ${spacing.xs}px;
font-style: italic;
`;