diff --git a/.gitignore b/.gitignore index 655e6d5..367dbf9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,13 @@ package.lock # testing /coverage +/test-results/ +/playwright-report/ +/blob-report/ # next.js /.next/ +/.next-test/ /out/ # production @@ -30,6 +34,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env*.local # vercel diff --git a/AGENTS.md b/AGENTS.md index 8a846aa..aae9527 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,8 @@ # AGENTS.md — Coffee Project Frontend +Primary Codex reference: [`../.codex/services/frontend.md`](/Users/daniilrakityansky/Documents/Work/Cofee/.codex/services/frontend.md). +If this file conflicts with the `.codex` guide, prefer the `.codex` guide. `CLAUDE.md` remains for Claude-specific tooling. + ## Project Overview Next.js 16 application using **Feature-Sliced Design (FSD)** architecture, powered by **Bun** runtime and package manager. diff --git a/CLAUDE.md b/CLAUDE.md index 54c65fe..825b609 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,6 +48,7 @@ Next.js 16 App Router with Feature-Sliced Design. Strict unidirectional imports: ## Component Convention Generate new components with `bun run gc ` — never create component files manually. Each component folder contains: + - `index.ts` — public re-export only - `ComponentName.tsx` — implementation - `ComponentName.module.scss` — scoped styles @@ -89,6 +90,7 @@ Use the shared `uploadFile` utility for any file upload — do not inline FormDa ```ts import { uploadFile } from "@shared/api/uploadFile" + const result = await uploadFile(file, "avatars") // result.file_url, result.file_path ``` @@ -102,8 +104,8 @@ Use `date-fns` with Russian locale for all date formatting — never use `moment ```ts import { formatDate, formatRelativeTime } from "@shared/lib/dates" -formatDate(user.date_joined) // "21.02.2026" -formatDate(date, "dd MMM yyyy") // "21 февр. 2026" +formatDate(user.date_joined) // "21.02.2026" +formatDate(date, "dd MMM yyyy") // "21 февр. 2026" formatRelativeTime(project.updated_at) // "2 дня назад" ``` @@ -124,3 +126,12 @@ All user-facing UI text **must be in Russian** — labels, headings, buttons, pl - **`next/image` remote hosts**: External image hostnames must be listed in `next.config.mjs` `images.remotePatterns`. MinIO (`localhost:9000`) is already configured. If you add another storage backend, add its hostname there too. - **Stale OpenAPI types**: Always run `bun run gen:api-types` before implementing against the API if the backend has changed. Stale types cause silent 404s at runtime. - **Never use raw `fetch`/`useEffect` for API calls** — always use `api.useQuery()`/`api.useMutation()` from `@shared/api` (TanStack Query + openapi-fetch wrapper). For polling, use the `refetchInterval` option. Raw `fetch` bypasses typed routes, auth middleware, and query caching. + +Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask. + +## Testing Standards + +- All E2E tests use Playwright with TypeScript +- Test files live in tests/e2e/ +- Use `getByRole` as primary locator strategy +- Every PR must include error-state tests, not just happy paths diff --git a/app/(protected)/loading.tsx b/app/(protected)/loading.tsx new file mode 100644 index 0000000..356a6c0 --- /dev/null +++ b/app/(protected)/loading.tsx @@ -0,0 +1,5 @@ +import { StaticLoader } from "@shared/ui/Loader" + +export default function ProtectedLoading() { + return +} diff --git a/app/(protected)/profile/loading.tsx b/app/(protected)/profile/loading.tsx new file mode 100644 index 0000000..0e57ab6 --- /dev/null +++ b/app/(protected)/profile/loading.tsx @@ -0,0 +1,29 @@ +import { Skeleton } from "@shared/ui/Skeleton" + +export default function ProfileLoading() { + return ( +
+
+ + + + +
+
+ ) +} diff --git a/app/(protected)/projects/[project_id]/page.tsx b/app/(protected)/projects/[project_id]/page.tsx index f8c3993..52b6aa6 100644 --- a/app/(protected)/projects/[project_id]/page.tsx +++ b/app/(protected)/projects/[project_id]/page.tsx @@ -1,11 +1,11 @@ import { JSX } from "react" -import { ProjectWorkspacePage } from "@pages/ProjectWorkspacePage" +import { ProjectWizardPage } from "@pages/ProjectWizardPage" export default function Projects(): JSX.Element { return (
- +
) } \ No newline at end of file diff --git a/app/(protected)/projects/loading.tsx b/app/(protected)/projects/loading.tsx new file mode 100644 index 0000000..eb64dc4 --- /dev/null +++ b/app/(protected)/projects/loading.tsx @@ -0,0 +1,18 @@ +import { ProjectCardSkeleton } from "@shared/ui/Skeleton" + +export default function ProjectsLoading() { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ) +} diff --git a/app/layout.tsx b/app/layout.tsx index 594d114..c963612 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next" import type { ReactNode } from "react" -import { Open_Sans } from "next/font/google" +import { Manrope } from "next/font/google" import "@shared/styles/global.scss" @@ -12,10 +12,11 @@ export const metadata: Metadata = { description: "Standalone Next.js app using FSD structure", } -const open_sans = Open_Sans({ +const manrope = Manrope({ + subsets: ["latin", "cyrillic"], preload: true, display: "swap", - variable: "--font-open-sans", + variable: "--font-manrope", }) export default function RootLayout({ @@ -24,7 +25,7 @@ export default function RootLayout({ children: ReactNode }>) { return ( - +