Merge branch 'master' of github.com:tmshv/hudozka

This commit is contained in:
tmshv committed 2026-04-25 01:34:14 +03:00
commit 35e1d762ad
255 files changed
+3496 -228

No files matched your search

+1
View File
@@ -39,6 +39,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: ./
file: ./apps/hudozka/Dockerfile
push: true
platforms: linux/amd64
tags: |
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
run: npm ci
- name: Type check
run: npx tsc --noEmit
run: npx tsc --noEmit -p apps/hudozka/tsconfig.json
build:
name: Build
+27 -35
View File
@@ -8,11 +8,18 @@ Website for the Art School of Shlisselburg ([art.shlisselburg.org](https://art.s
## Commands
From repo root (delegates to `apps/hudozka`):
- `npm run dev` — dev server with Turbo (`next dev --turbo`)
- `npm run build` — production build
- `npm run lint` — ESLint checks
- `npm run lint` — ESLint across the whole monorepo
- `npm run format` — auto-fix lint issues
- `npm test` — run unit tests (Vitest, non-watch mode)
- `npm test` — run unit tests (Vitest) across every workspace
Per-app:
- `npm run dev -w apps/hudozka-writer` — writer UI (Vite)
- `npm run migrate -w apps/migrate-to-pocketbase` — one-off migration script
Node 24 required (see `mise.toml`). Deployed on Vercel.
@@ -22,9 +29,9 @@ Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue n
## CI/CD
- `Dockerfile` — multi-stage Alpine build with standalone Next.js output, pushed to GHCR
- `.github/workflows/build-app.yaml` — builds and pushes Docker image on push to master
- `.github/workflows/test-app.yml` — runs lint, tests, and build on PRs (build runs only after lint and tests pass)
- `apps/hudozka/Dockerfile` — multi-stage Alpine build with standalone Next.js output, pushed to GHCR. Build context is the repo root so the workspace graph is available.
- `.github/workflows/build-app.yaml` — builds and pushes Docker image on push to master (`file: ./apps/hudozka/Dockerfile`, `context: ./`).
- `.github/workflows/test-app.yml` — runs lint, tests, typecheck, and build on PRs.
## Code Style
@@ -62,16 +69,20 @@ When adding or updating packages in `package.json`, do not specify the PATCH ver
## Key Directories
- `src/app/` — App Router pages, layouts, and route handlers (layout, home page, `[...slug]` catch-all, not-found, sitemap, robots, feed)
- `src/components/` — page-level and feature components (folder-per-component with `index.tsx` + `styles.module.css`)
- `src/ui/` — reusable primitives (Box, Button, Picture, Panel, Overlay, Title, ThemeColor)
- `src/remote/` — CMS integration (`api.ts` for fetching, `factory.ts` for transforms, `types.ts` for response shapes, `pb.ts` for the PocketBase client)
- `src/store/` — Valtio stores (theme/accessibility, playback)
- `src/hooks/` — custom hooks (`useMobile`, `useAccessibility`, `useDarkTheme`, `useReducedMotion`, `useToggle`, `useLockBodyScroll`, `useMediaQuery`)
- `src/lib/` — pure utility functions (date, string, url, image, file, array, math, text, meta helpers)
- `src/style/` — global CSS with CSS custom properties for theming
- `modules/hudozka-writer/` — Vite-based Tiptap editor (ancillary)
- `modules/migrate-to-pocketbase/` — one-off Strapi → PocketBase migration script
- `apps/hudozka/src/app/` — App Router pages, layouts, route handlers (catch-all, not-found, sitemap, robots, feed)
- `apps/hudozka/src/components/` — page-level and feature components (folder-per-component + `styles.module.css`)
- `apps/hudozka/src/ui/` — Next/store-coupled UI that stays in the app: `Picture`, `ThemeColor`
- `apps/hudozka/src/remote/` — PocketBase API client (`api.ts`, `factory.ts`, `types.ts`, `pb.ts`)
- `apps/hudozka/src/store/` — Valtio stores (theme/accessibility, playback)
- `apps/hudozka/src/hooks/` — business-coupled hooks: `useDarkTheme`, `useAccessibility`
- `apps/hudozka/src/lib/` — app-only lib (currently just `meta.ts`)
- `apps/hudozka/src/style/` — global CSS with CSS custom properties for theming
- `packages/ui/` — `@hudozka/ui` — reusable primitives (Box, Button, Panel, Title, Overlay)
- `packages/hooks/` — `@hudozka/hooks` — generic React hooks (useToggle, useLockBodyScroll, useMediaQuery, useMobile, useReducedMotion)
- `packages/utils/` — `@hudozka/utils` — pure utilities (array, string, url, math, image, file, date)
- `packages/text/` — `@hudozka/text` — markdown + typograf rendering
- `apps/hudozka-writer/` — Vite-based Tiptap editor
- `apps/migrate-to-pocketbase/` — one-off Strapi → PocketBase migration script
- `pb/` — PocketBase instance (data, migrations)
## Styling
@@ -80,7 +91,7 @@ CSS Modules (`.module.css`) for component-scoped styles. Global theme via CSS cu
## TypeScript
Path alias: `@/*` → `src/*`. Strict mode enabled. Target ES2020. `moduleDetection: "force"` in tsconfig (required because Next.js auto-generates `.next/types/validator.ts` without exports, which breaks `verbatimModuleSyntax`). Use `@/` path alias for all local imports, not `src/`. Legacy `src/` imports exist in the codebase but should not be used in new or modified code.
Path alias: `@/*` → `src/*`. Strict mode enabled. Target ES2020. `moduleDetection: "force"` in tsconfig (required because Next.js auto-generates `.next/types/validator.ts` without exports, which breaks `verbatimModuleSyntax`). Use `@/` path alias for all local imports, not `src/`. Legacy `src/` imports exist in the codebase but should not be used in new or modified code. The `@/*` path alias is only configured in `apps/hudozka/tsconfig.json`; packages under `packages/*` use short relative imports.
`verbatimModuleSyntax` is enabled — use `import type` for type-only imports. When importing both values and types from the same module, use separate `import` and `import type` statements:
@@ -111,22 +122,3 @@ Active modernization is tracked in GitHub issues #222–#238. Key items:
- Some inline styles in `src/components/App/index.tsx` should be CSS modules.
- `react-use` and `use-media` dependencies are unmaintained; only `useToggle` and `useLockBodyScroll` are used from `react-use`.
## Planned: Monorepo Migration
A migration to an npm-workspaces monorepo is planned but not yet executed. Spec and plan:
- Design: `docs/specs/20260424-monorepo-design.md`
- Implementation plan: `docs/plans/20260424-monorepo.md`
Target layout (post-migration):
- `apps/hudozka/` — this Next.js app
- `apps/hudozka-writer/` — Vite-based Tiptap editor (from `modules/hudozka-writer`)
- `apps/migrate-to-pocketbase/` — migration script (from `modules/migrate-to-pocketbase`)
- `packages/ui/` — `@hudozka/ui`: Box, Button, Panel, Title, Overlay
- `packages/hooks/` — `@hudozka/hooks`: generic React hooks
- `packages/utils/` — `@hudozka/utils`: array, string, url, math, image, file, date
- `packages/text/` — `@hudozka/text`: markdown-it + typograf wrappers
- `pb/` — untouched
Packages are consumed as TypeScript source (`"exports": { ".": "./src/index.ts" }`), no build step. `Picture` and `ThemeColor` stay in the web app (Next/Valtio-coupled). The `@/` path alias is only configured in `apps/hudozka/tsconfig.json`; packages use short relative imports. `Task 7.3` of the plan replaces this section with the post-migration version of Commands / Key Directories / CI/CD.
+22
View File
@@ -4,6 +4,28 @@ Website for the Art School of Shlisselburg. Built with Next.js and powered by Po
Font: [«Shadow»](http://mishapanfilov.ru/font_shadow.html)
## Repo layout
This is an npm-workspaces monorepo:
- `apps/hudozka` — main Next.js app (art.shlisselburg.org)
- `apps/hudozka-writer` — Vite-based content editor
- `apps/migrate-to-pocketbase` — one-off migration script (Strapi → PocketBase)
- `packages/ui` — `@hudozka/ui` — shared React primitives (Box, Button, Panel, Title, Overlay)
- `packages/hooks` — `@hudozka/hooks` — generic React hooks
- `packages/utils` — `@hudozka/utils` — pure utilities (array, string, url, math, image, file, date)
- `packages/text` — `@hudozka/text` — markdown + typograf rendering
- `pb/` — PocketBase instance (untouched by the monorepo migration)
Common commands (from repo root):
- `npm run dev` — run the web app dev server
- `npm run build` — production build of the web app
- `npm run lint` — lint the whole monorepo
- `npm test` — run tests across every workspace
- `npm run dev -w apps/hudozka-writer` — run the writer
- `npm run migrate -w apps/migrate-to-pocketbase` — run the migration script
## Development
### Prerequisites
View File
Whitespace-only changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,18 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+15 -12
View File
@@ -6,45 +6,47 @@ FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
# Copy workspace manifests (root + every workspace) to leverage npm ci caching.
COPY package.json package-lock.json ./
COPY apps/hudozka/package.json ./apps/hudozka/
COPY apps/hudozka-writer/package.json ./apps/hudozka-writer/
COPY apps/migrate-to-pocketbase/package.json ./apps/migrate-to-pocketbase/
COPY packages/ ./packages/
RUN npm ci
RUN npm install --os=linux --libc=musl --cpu=x64 sharp
RUN npm install --os=linux --libc=musl --cpu=x64 sharp --workspace apps/hudozka
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/hudozka/node_modules ./apps/hudozka/node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
RUN npm run build -w apps/hudozka
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
# sharp is required to be installed in standalone mode for the image optimization to function correctly.
# Read more at: https://nextjs.org/docs/messages/sharp-missing-in-production
# RUN npm install sharp
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/apps/hudozka/public ./apps/hudozka/public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN mkdir -p apps/hudozka/.next
RUN chown nextjs:nodejs apps/hudozka/.next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/hudozka/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/hudozka/.next/static ./apps/hudozka/.next/static
USER nextjs
@@ -54,4 +56,5 @@ ENV PORT=3000
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
ENV HOSTNAME="0.0.0.0"
WORKDIR /app/apps/hudozka
CMD ["node", "server.js"]
View File
File renamed without changes.
@@ -1,9 +1,12 @@
import type { NextConfig } from "next"
import path from "node:path"
const config: NextConfig = {
output: "standalone",
outputFileTracingRoot: path.join(process.cwd(), "../../"),
trailingSlash: false,
images: {
unoptimized: true,
remotePatterns: [
{ hostname: "hudozka.shlisselburg.org" },
{ hostname: "images.weserv.nl" },
+43
View File
@@ -0,0 +1,43 @@
{
"name": "hudozka",
"private": true,
"version": "7.0.0",
"type": "module",
"description": "",
"scripts": {
"start": "next start",
"dev": "next dev --turbo",
"build": "next build",
"lint": "eslint .",
"format": "eslint --fix .",
"test": "vitest run"
},
"repository": {
"type": "git",
"url": "https://github.com/tmshv/hudozka"
},
"author": "Roman Timashev",
"license": "ISC",
"dependencies": {
"@hudozka/hooks": "*",
"@hudozka/text": "*",
"@hudozka/ui": "*",
"@hudozka/utils": "*",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-tooltip": "^1.2.0",
"classnames": "^2.5.0",
"dangerously-set-html-content": "^1.1.0",
"next": "^16.2.0",
"pocketbase": "^0.26.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.5.0",
"sharp": "^0.34.0",
"valtio": "^2.3.0"
},
"overrides": {
"dangerously-set-html-content": {
"react": "$react"
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -4,7 +4,7 @@ import { App } from "@/components/App"
import { PageContent } from "@/components/PageContent"
import { MetaBuilder, buildMetadata } from "@/lib/meta"
import { getPageBySlug, getUrls } from "@/remote/api"
import { tail } from "@/lib/array"
import { tail } from "@hudozka/utils"
export const revalidate = 30
export const dynamicParams = true
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
import { theme, toggleTheme } from "@/store/theme"
import { Button } from "@/ui/Button"
import { Button } from "@hudozka/ui"
import { HiOutlineEye, HiOutlineEyeOff } from "react-icons/hi"
import { useSnapshot } from "valtio"
@@ -8,16 +8,14 @@ import { Content } from "../Content"
import { Breadcrumbs } from "../Breadcrumbs"
import type { BreadcrumbPart } from "@/types"
import { usePathname } from "next/navigation"
import { useMobile } from "@/hooks/useMobile"
import { useMobile } from "@hudozka/hooks"
import { Navigation } from "@/components/Navigation"
import { Box } from "@/ui/Box"
import { Box, Panel, Title } from "@hudozka/ui"
import { AccessibilityButton } from "../AccessibilityButton"
import { Copyright } from "@/components/Copyright"
import { yearRange } from "@/const"
import { Spacer } from "../Spacer"
import { Author } from "../Author"
import { Title } from "@/ui/Title"
import { Panel } from "@/ui/Panel"
import { Contacts } from "../Contacts"
import dynamic from "next/dynamic"
import Link from "next/link"
File renamed without changes.
@@ -1,8 +1,8 @@
import s from "./styles.module.css"
import type { BreadcrumbPart } from "@/types"
import { Button } from "@/ui/Button"
import { insertBetween } from "@/lib/array"
import { Button } from "@hudozka/ui"
import { insertBetween } from "@hudozka/utils"
export type BreadcrumbsProps = {
style?: React.CSSProperties
Loaded 100 of 255 files, more files were not shown because too many files have changed in this diff. Show more