Ниже представлено описание серверной части проекта, который представляет собой веб-сайт для отображения бизнес-метрик. На данный момент здесь реализована авторизация и аутентификация. Я хочу чтобы ты помог мне реализовать клиентскую часть. Тех. стек - NextJS, Tailwind. Тебе нужно предоставить полностью готовое решение, чтобы можно было зарегистрировать нового пользователя, войти в систему, попасть на основную страницу. Клиентская часть не должна быть сложной, наоборот, чем проще - тем лучше. Предоставь итоговую структуру фронтенда. Сервер написан на python + fastAPI, бд - Postgresql.

models.py:
# app/models.py
from sqlalchemy import Column, Integer, String, DateTime, Enum, Boolean, ForeignKey, Text, CheckConstraint, Index, func, BIGINT, DECIMAL, DOUBLE_PRECISION
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from .database import Base
import enum

# Переносим Enum типы из SQL в Python Enum для использования в моделях
class UserRoleEnum(str, enum.Enum):
    admin = "admin"
    analyst = "analyst"
    viewer = "viewer"

class FileTypeEnum(str, enum.Enum):
    csv = "csv"
    json = "json"

class UploadStatusEnum(str, enum.Enum):
    pending = "pending"
    processing = "processing"
    completed = "completed"
    failed = "failed"

class ChartTypeEnum(str, enum.Enum):
    line = "line"
    bar = "bar"
    pie = "pie"
    kpi_value = "kpi_value"

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, index=True, nullable=False)
    email = Column(String(255), unique=True, index=True, nullable=False)
    hashed_password = Column(String(255), nullable=False)
    role = Column(Enum(UserRoleEnum), nullable=False, default=UserRoleEnum.viewer)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    # Связи (добавятся позже для других моделей)
    uploads = relationship("DataUpload", back_populates="owner")
    custom_metrics = relationship("Metric", back_populates="creator")
    dashboards = relationship("Dashboard", back_populates="owner")

# --- Добавьте остальные модели (DataUpload, Metric, MetricValue, Dashboard, DashboardItem) ---
# --- аналогично User, используя типы Column и связи relationship ---
# Пример для DataUpload:
class DataUpload(Base):
    __tablename__ = "data_uploads"
    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    filename = Column(String(255), nullable=False)
    file_type = Column(Enum(FileTypeEnum), nullable=False)
    upload_time = Column(DateTime(timezone=True), server_default=func.now())
    status = Column(Enum(UploadStatusEnum), nullable=False, default=UploadStatusEnum.pending)
    storage_path = Column(String(512), nullable=True)
    processing_log = Column(Text, nullable=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    owner = relationship("User", back_populates="uploads")
    metric_values = relationship("MetricValue", back_populates="source_upload")


# app/schemas.py
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
from .models import UserRoleEnum # Импортируем Enum из моделей

# --- User Schemas ---
class UserBase(BaseModel):
    email: EmailStr
    username: str = Field(..., min_length=3, max_length=50)
    role: UserRoleEnum

class UserCreate(UserBase):
    password: str = Field(..., min_length=8)

class UserUpdate(BaseModel):
    email: Optional[EmailStr] = None
    username: Optional[str] = Field(None, min_length=3, max_length=50)
    password: Optional[str] = Field(None, min_length=8) # Для смены пароля

# Схема для возврата данных пользователя API (без пароля)
class User(UserBase):
    id: int
    role: UserRoleEnum
    created_at: datetime
    updated_at: datetime

    class Config:
        # Указываем Pydantic, что модель может быть создана из ORM объекта
        # Раньше было orm_mode = True
        from_attributes = True

# --- Token Schemas ---
class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: Optional[str] = None

# --- Схемы для Data Upload ---
class DataUploadBase(BaseModel):
    filename: str
    file_type: str # Или используйте FileTypeEnum

class DataUploadCreate(DataUploadBase):
    pass # Данные приходят из File(...)

class DataUpload(DataUploadBase):
    id: int
    user_id: int
    upload_time: datetime
    status: str # Или UploadStatusEnum
    storage_path: Optional[str] = None
    processing_log: Optional[str] = None
    created_at: datetime

    class Config:
        from_attributes = True

main.py:
app = FastAPI(
    title=settings.PROJECT_NAME,
    openapi_url=f"{settings.API_V1_STR}/openapi.json" # Путь к спецификации OpenAPI
)

# Создаем таблицы в базе данных
Base.metadata.create_all(bind=engine)

# Настройка CORS
if settings.BACKEND_CORS_ORIGINS:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
        allow_credentials=True,
        allow_methods=["*"], # Разрешить все методы (GET, POST, etc.)
        allow_headers=["*"], # Разрешить все заголовки
    )

# Подключение роутеров
app.include_router(auth.router, prefix=settings.API_V1_STR + "/auth", tags=["Auth"])
# Подключите другие роутеры:
# app.include_router(users.router, prefix=settings.API_V1_STR + "/users", tags=["Users"])
# app.include_router(uploads.router, prefix=settings.API_V1_STR + "/uploads", tags=["Data Uploads"])
# app.include_router(metrics.router, prefix=settings.API_V1_STR + "/metrics", tags=["Metrics"])
# app.include_router(dashboards.router, prefix=settings.API_V1_STR + "/dashboards", tags=["Dashboards"])


# Корневой эндпоинт для проверки
@app.get("/", tags=["Root"])
async def read_root():
    return {"message": f"Welcome to {settings.PROJECT_NAME} API"}


# app/crud.py
from sqlalchemy.orm import Session
from . import models, schemas
from .security import get_password_hash # Импортируем функцию хэширования

def get_user(db: Session, user_id: int):
    return db.query(models.User).filter(models.User.id == user_id).first()

def get_user_by_email(db: Session, email: str):
    return db.query(models.User).filter(models.User.email == email).first()

def get_user_by_username(db: Session, username: str):
    return db.query(models.User).filter(models.User.username == username).first()

def get_users(db: Session, skip: int = 0, limit: int = 100):
    return db.query(models.User).offset(skip).limit(limit).all()

def create_user(db: Session, user: schemas.UserCreate):
    hashed_password = get_password_hash(user.password)
    # Создаем объект модели SQLAlchemy
    db_user = models.User(
        email=user.email,
        username=user.username,
        hashed_password=hashed_password,
        # Можно добавить роль по умолчанию или передавать ее в UserCreate
        role=user.role
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user) # Обновляем объект из БД (чтобы получить id, created_at)
    return db_user

# --- Добавьте CRUD функции для других моделей ---
# Например, create_metric, get_metric_by_name, create_metric_value, get_metric_values_by_metric_id и т.д.


# app/routers/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm # Стандартная форма для /token эндпоинта
from sqlalchemy.orm import Session
from datetime import timedelta

from .. import crud, schemas, models
from ..database import get_db
from ..security import create_access_token, verify_password, get_current_active_user
from ..core.config import settings

router = APIRouter()

@router.post("/register", response_model=schemas.User, status_code=status.HTTP_201_CREATED)
async def register_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    """
    Регистрация нового пользователя.
    """
    db_user_email = crud.get_user_by_email(db, email=user.email)
    if db_user_email:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
    db_user_username = crud.get_user_by_username(db, username=user.username)
    if db_user_username:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Username already taken")

    created_user = crud.create_user(db=db, user=user)
    return created_user

@router.post("/login", response_model=schemas.Token)
async def login_for_access_token(
    db: Session = Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
):
    """
    Аутентификация пользователя и выдача JWT токена.
    Использует OAuth2PasswordRequestForm, ожидая 'username' и 'password' в form-data.
    """
    user = crud.get_user_by_username(db, username=form_data.username)
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )

    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@router.get("/users/me", response_model=schemas.User)
async def read_users_me(current_user: models.User = Depends(get_current_active_user)):
    """
    Получение информации о текущем аутентифицированном пользователе.
    """
    return current_user

# Добавьте эндпоинт для logout, если нужно (например, блэклист токенов, но это усложнение)
# Обычно logout реализуется на фронтенде удалением токена из хранилища.