chore: restructure project into backend and frontend folders

- Move all frontend code to frontend/ directory
- Add backend/ with Go project structure (cmd, internal, pkg)
- Add docker-compose.yml for orchestration
This commit is contained in:
Egor Pozharov
2026-04-14 13:14:28 +06:00
parent 11e12f964d
commit 4e0899d3ce
54 changed files with 779 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import type { ButtonHTMLAttributes, ReactNode } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'tertiary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
children: ReactNode;
}
export const Button = ({
variant = 'primary',
size = 'md',
children,
className = '',
...props
}: ButtonProps) => {
const baseStyles = 'inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-[#1B263B] text-white hover:bg-[#2a3a52] focus:ring-[#1B263B]',
secondary: 'bg-[#f0edef] text-[#1b1b1d] hover:bg-[#e4e2e4] focus:ring-[#75777d]',
tertiary: 'bg-[#F28C28] text-white hover:bg-[#d97a1f] focus:ring-[#F28C28]',
ghost: 'bg-transparent text-[#1b1b1d] hover:bg-[#f5f3f5] focus:ring-[#75777d]',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
{...props}
>
{children}
</button>
);
};

View File

@@ -0,0 +1,22 @@
import type { ReactNode } from 'react';
interface CardProps {
children: ReactNode;
className?: string;
padding?: 'none' | 'sm' | 'md' | 'lg';
}
export const Card = ({ children, className = '', padding = 'md' }: CardProps) => {
const paddings = {
none: '',
sm: 'p-3',
md: 'p-4',
lg: 'p-6',
};
return (
<div className={`bg-white rounded-lg shadow-sm border border-[#e4e2e4] ${paddings[padding]} ${className}`}>
{children}
</div>
);
};

View File

@@ -0,0 +1,25 @@
import type { InputHTMLAttributes } from 'react';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export const Input = ({ label, error, className = '', ...props }: InputProps) => {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-[#1b1b1d] mb-1">
{label}
</label>
)}
<input
className={`w-full px-3 py-2 bg-[#f5f3f5] border border-[#c5c6cd] rounded-md text-[#1b1b1d] placeholder-[#75777d] focus:outline-none focus:ring-2 focus:ring-[#1B263B] focus:border-transparent transition-colors ${className}`}
{...props}
/>
{error && (
<p className="mt-1 text-sm text-red-600">{error}</p>
)}
</div>
);
};

View File

@@ -0,0 +1,44 @@
import { X } from 'lucide-react';
import type { ReactNode } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: ReactNode;
footer?: ReactNode;
}
export const Modal = ({ isOpen, onClose, title, children, footer }: ModalProps) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<div
className="fixed inset-0 bg-black/30 transition-opacity"
onClick={onClose}
/>
<div className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all w-full max-w-lg">
<div className="flex items-center justify-between border-b border-[#e4e2e4] px-4 py-3">
<h3 className="text-lg font-semibold text-[#1b1b1d]">{title}</h3>
<button
onClick={onClose}
className="rounded-full p-1 hover:bg-[#f5f3f5] transition-colors"
>
<X size={20} className="text-[#75777d]" />
</button>
</div>
<div className="px-4 py-4">
{children}
</div>
{footer && (
<div className="border-t border-[#e4e2e4] px-4 py-3 flex justify-end gap-2">
{footer}
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,37 @@
import type { SelectHTMLAttributes } from 'react';
interface SelectOption {
value: string;
label: string;
}
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
label?: string;
options: SelectOption[];
error?: string;
}
export const Select = ({ label, options, error, className = '', ...props }: SelectProps) => {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-[#1b1b1d] mb-1">
{label}
</label>
)}
<select
className={`w-full px-3 py-2 bg-[#f5f3f5] border border-[#c5c6cd] rounded-md text-[#1b1b1d] focus:outline-none focus:ring-2 focus:ring-[#1B263B] focus:border-transparent transition-colors ${className}`}
{...props}
>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
{error && (
<p className="mt-1 text-sm text-red-600">{error}</p>
)}
</div>
);
};

View File

@@ -0,0 +1,5 @@
export { Button } from './Button';
export { Card } from './Card';
export { Modal } from './Modal';
export { Input } from './Input';
export { Select } from './Select';