import { SERTIFICATE_PRICE } from '@/shared/lib/metadata';
import { useTranslations } from 'next-intl';
import React, { useEffect } from 'react';
// ─── FieldWrapper ────────────────────────────────────────────────────────────
interface FieldWrapperProps {
label: string;
htmlFor?: string;
error?: string;
required?: boolean;
children: React.ReactNode;
}
export function FieldWrapper({
label,
htmlFor,
error,
required,
children,
}: FieldWrapperProps) {
return (
{children}
{error && (
{error}
)}
);
}
// ─── TextInput ───────────────────────────────────────────────────────────────
interface TextInputProps extends React.InputHTMLAttributes {
hasError?: boolean;
}
export function TextInput({
hasError,
className = '',
...props
}: TextInputProps) {
return (
);
}
// ─── ReadonlyField ───────────────────────────────────────────────────────────
interface ReadonlyFieldProps {
value: string;
icon?: React.ReactNode;
autoFilledText?: string;
}
export function ReadonlyField({
value,
icon,
autoFilledText = 'Auto-filled',
}: ReadonlyFieldProps) {
return (
{icon && {icon}}
{value}
{autoFilledText}
);
}
// ─── FileUploadField ─────────────────────────────────────────────────────────
interface FileUploadFieldProps {
file: File | null;
onFileChange: (file: File | null) => void;
hasError?: boolean;
accept?: string;
clickToUploadText?: string;
fileTypesText?: string;
removeFileAriaLabel?: string;
}
export function FileUploadField({
file,
onFileChange,
hasError,
accept = '.pdf,.doc,.docx,.txt',
clickToUploadText = 'Click to upload document',
fileTypesText = 'PDF, DOC, DOCX, TXT · Max 20 MB',
removeFileAriaLabel = 'Remove file',
}: FileUploadFieldProps) {
const inputRef = React.useRef(null);
const handleChange = (e: React.ChangeEvent) => {
const selected = e.target.files?.[0] ?? null;
onFileChange(selected);
// Reset so the same file can be re-selected after removal
e.target.value = '';
};
const handleRemove = () => {
onFileChange(null);
};
const formatBytes = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
return (
{!file ? (
) : (
{file.name}
{formatBytes(file.size)}
)}
);
}
// ─── CertificateCheckbox ─────────────────────────────────────────────────────
interface CertificateCheckboxProps {
checked: boolean;
onChange: () => void;
title?: string;
description?: string;
}
export function CertificateCheckbox({
checked,
onChange,
title = 'Return result with certificate',
description = 'An official certificate will be attached to your originality report.',
}: CertificateCheckboxProps) {
const t = useTranslations('PlagiarismCheck');
return (
);
}
// ─── SubmitButton ────────────────────────────────────────────────────────────
interface SubmitButtonProps {
isLoading: boolean;
submittingText?: string;
submitText?: string;
}
export function SubmitButton({
isLoading,
submittingText = 'Submitting…',
submitText = 'Submit for Originality Check',
}: SubmitButtonProps) {
return (
);
}
// ─── StatusBanner ────────────────────────────────────────────────────────────
interface StatusBannerProps {
status: 'success' | 'error';
message: string;
onDismiss: () => void;
dismissText?: string;
}
export function StatusBanner({
status,
message,
onDismiss,
dismissText = 'Dismiss',
}: StatusBannerProps) {
const isSuccess = status === 'success';
useEffect(() => {
setTimeout(onDismiss, 3000);
}, []);
return (
{isSuccess ? '✓' : '✕'}
{message}
);
}
// ─── Inline SVG Icons ────────────────────────────────────────────────────────
function UploadIcon({ className = '' }: { className?: string }) {
return (
);
}
function DocumentIcon({ className = '' }: { className?: string }) {
return (
);
}
function XIcon() {
return (
);
}
function ShieldIcon() {
return (
);
}
function SpinnerIcon() {
return (
);
}