Compare commits

...
9 Commits
Author SHA1 Message Date
Do SikiandClaude Sonnet 5 018a1f2767 sync: mark MITHOME-87 done in TODO.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:42:12 +02:00
Do SikiandClaude Sonnet 5 3132fcb8eb feat(cms): Home/About/Services/Contact/Common Globals + JSON migration (MITHOME-87)
Payload Global configs mirroring proto/src/content/types.ts:
- src/globals/{Home,About,Services,Contact,Common}.ts
- src/globals/fields/stringArray.ts — shared helper: Payload has no
  native string[] field, so every plain string array from the old
  content types (trustBullets, paragraphs, features, spec items, …)
  becomes an array of one-field { value } objects.
- home.partners is intentionally NOT included — that becomes its own
  Partners collection (logo -> Media upload) in MITHOME-89, to avoid
  two disagreeing sources for the same data.
- contact.form.fields.gdpr.label stays a plain textarea (not lexical
  richText): the JSON source is a hand-written HTML string with an
  <a> tag; richText's node-tree serialization would need its own
  migration/render logic, out of scope for this bootstrap pass.

scripts/migrate-content-to-payload.ts: one-shot, idempotent Local API
migration reading the existing JSON files and calling updateGlobal —
does not touch or delete the JSON files. Run via the new
`npm run migrate:content` script.

Registered the five Globals in payload.config.ts.

Also included here (belongs with the previous "resolve double-root-
layout conflict" commit but didn't actually get staged there —
verified only now by diffing HEAD against the working tree):
(frontend)/layout.tsx's relative imports corrected to ../../ instead
of ../ (one directory deeper than the original src/app/layout.tsx).

Verified:
- npm run build / lint, tsc --noEmit, npm test (58 passed) all clean
- npm run migrate:content against the real dev MongoDB container,
  then read back via payload.findGlobal() — hero.title, trustBullets,
  services.items[0].features, footer.address, faq.items.length all
  match the JSON source
- Real browser: logged into /admin, opened the Home global editor —
  Hero/Trust bullets group renders and shows the migrated Hungarian
  content correctly (see screenshot shared in chat)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:41:03 +02:00
Do SikiandClaude Sonnet 5 3183098926 chore(deps): pin tsx 4.23.13 and add migrate:content script
tsx 4.22.4 (the version resolved before this was an explicit
dependency) fails on Node v25.6.1 with "T.registerHooks is not a
function" — its CommonJS-require hook feature-detection finds
node:module's registerHooks but calls it with a shape that version of
Node no longer accepts. This broke both the `payload` CLI (needed for
`generate:importmap`, see next commit) and would have broken
`npm run migrate:content` (MITHOME-87) the same way. 4.23.13 fixes it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:40:24 +02:00
Do SikiandClaude Sonnet 5 a661739844 fix(cms): resolve double-root-layout conflict between the site and Payload admin
Next.js 16 requires either one shared root layout for every route, or
each top-level route group to be fully self-contained (no ancestor
layout.tsx above it). MITHOME-86 added (payload)/layout.tsx — which
itself renders Payload's own <html>/<body> via <RootLayout> — while
src/app/layout.tsx still existed as a shared ancestor and did the same.
The result was two React trees fighting over <html>/<body>/<head>,
surfacing in the browser as hydration failures and "You are mounting a
new html component" errors on /admin (invisible to curl-based checks
in the MITHOME-86 verification, since they only exercise SSR, not
client hydration — that verification's "confirmed live" claim was
therefore incomplete; caught now with an actual browser).

Fix, per Payload's documented multi-root pattern: move every existing
site route (page.tsx, globals.css, kapcsolat/szolgaltatasok/rolunk/
adatvedelem/felhasznalasi-feltetelek) into a new (frontend) route
group with its own layout.tsx (renamed from src/app/layout.tsx, with
relative imports adjusted one level deeper), as a sibling of (payload).
No shared layout.tsx remains directly under src/app/.

Verified in a real browser (not just curl): /admin/login and the
dashboard now render and hydrate cleanly, and all site routes
(/, /rolunk, /szolgaltatasok, /kapcsolat, /adatvedelem,
/felhasznalasi-feltetelek) are unaffected — same output, just moved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:40:00 +02:00
Do SikiandClaude Sonnet 5 2a4f33172b sync: mark MITHOME-86 done in TODO.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:21:32 +02:00
Do SikiandClaude Sonnet 5 849acf4c34 fix(config): disable Next.js 16 auto-generated agent-rules files
\`next dev\` (Next.js 16) writes proto/AGENTS.md and proto/CLAUDE.md on
every run to brief AI agents on framework changes. This project already
has its own agent instruction system (root CLAUDE.md -> .agent/) — a
second, unrelated proto/CLAUDE.md stub would conflict with/shadow it
for anyone working inside proto/. Disabled via agentRules: false.

Verified live: MITHOME-86's admin route renders end-to-end against a
real MongoDB (docker-compose.dev.yml mongodb service) — GET /admin 200
with the create-first-user flow, GET /api/users correctly 403s for an
anonymous request, /api/health unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:19:25 +02:00
Do SikiandClaude Sonnet 5 1600d99c5d feat(cms): Payload CMS bootstrap — config, MongoDB adapter, admin routes (MITHOME-86)
Alapinstalláció a Payload CMS-re való áttéréshez (EPIC MITHOME-85):

- payload@3.88.0, @payloadcms/next, @payloadcms/db-mongodb,
  @payloadcms/richtext-lexical, graphql, sharp telepítve
- src/payload.config.ts: mongooseAdapter a meglévő MONGODB_URI-ra
  (ugyanaz az adatbázis, mint a Mongoose/mongodb rétegnek), lexical
  editor, PAYLOAD_SECRET env-ből
- src/collections/Users.ts: minimális auth collection — Payload nem
  tud admin felületet renderelni auth collection nélkül. Ez csak a
  bootstraphez kell; a valódi access control/jelszó-politika MITHOME-90
  feladata.
- App Router route group (src/app/(payload)/): admin UI
  ([[...segments]]), REST (api/[...slug]), GraphQL + playground route-ok,
  root layout — a szokásos Payload v3 Next.js integrációs minta szerint
- next.config.ts: withPayload() wrapper a route handler bundling-hoz
- tsconfig.json: @payload-config path alias -> src/payload.config.ts
  (ez oldja fel a webpack/turbopack importot is, nem csak a type-checket)
- PAYLOAD_SECRET env var: generált dev érték a .env.local-ban
  (gitignore-olt), changeme placeholder + generálási megjegyzés a
  staging/production .env példafájlokban, dokumentálva a CLAUDE.md
  env-lista részében

Ismert, még nem javított biztonsági advisory a felvett payload@3.88.0-ban
(GHSA-jg8r-5jh2-v2xj, moderate, CWE-307: az admin account-unlock alapból
más fiókok lockoutját is felold hitelesített usernek) — nincs újabb
patch-elt verzió jelenleg, nyomon követve MITHOME-90 alatt.

Ellenőrizve ezen a commiton: npm run build, npm run lint, tsc --noEmit,
npm test (58 passed) — mind zöld. Az admin bejelentkezés/DB-kapcsolat
élő tesztje MongoDB-t igényel (jelen környezetben Docker daemon nem fut,
ez lokálisan `docker compose -f docker-compose.dev.yml up -d mongodb`
után `npm run dev` + http://localhost:3000/admin-mal ellenőrizhető).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:14:51 +02:00
Do SikiandClaude Sonnet 5 30364fcab7 sync: update TODO.md with Payload CMS epic (MITHOME-85..97)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:14:31 +02:00
Do SikiandClaude Sonnet 5 6cb99f603a chore(deps): upgrade Next.js to 16.3.4 for Payload CMS compatibility
Payload 3.88.0's peer range for `next` is >=15.2.9 <15.3.0 ||
>=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.6 <17.0.0 — the
project's previous 15.5.23 fell in the unsupported gap between the
15.4.x and 16.2.x ranges. Upgrading to 16.3.4 (React 19.1.0 stays
compatible) unblocks MITHOME-86.

- next.config.ts: drop the removed `eslint.ignoreDuringBuilds` option
  (Next 16 no longer runs ESLint during `next build`)
- eslint.config.mjs: import eslint-config-next's native flat-config
  arrays directly instead of bridging through FlatCompat, which threw
  "Converting circular structure to JSON" under ESLint 9 with the
  upgraded config
- tsconfig.json: Next 16's own migration set `jsx: react-jsx` and added
  `.next/dev/types/**/*.ts` to `include`
- ThemeProvider.tsx: suppress two react-hooks/set-state-in-effect
  false positives (new rule shipped with eslint-config-next 16) —
  these effects intentionally sync state from localStorage/DOM on
  mount, there is no subscription to move the setState into

Verified: npm run build, npm run lint, tsc --noEmit, npm test (58
passed) all clean on this change alone, before installing Payload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 03:04:54 +02:00
35 changed files with 5087 additions and 498 deletions
+5
View File
@@ -7,6 +7,11 @@ MONGODB_DB=mozdit
MONGO_ROOT_USER=admin MONGO_ROOT_USER=admin
MONGO_ROOT_PASSWORD=changeme MONGO_ROOT_PASSWORD=changeme
# Payload CMS (MITHOME-86) — generálj egyedi, erős titkot ehhez a
# környezethez (pl. `openssl rand -hex 32`); ne ossz meg értéket másik
# environmenttel.
PAYLOAD_SECRET=changeme
# Publikus site URL (beég build időben a Dockerfile-ba) # Publikus site URL (beég build időben a Dockerfile-ba)
NEXT_PUBLIC_SITE_URL=https://mozdit.hu NEXT_PUBLIC_SITE_URL=https://mozdit.hu
+5
View File
@@ -7,6 +7,11 @@ MONGODB_DB=mozdit
MONGO_ROOT_USER=admin MONGO_ROOT_USER=admin
MONGO_ROOT_PASSWORD=changeme MONGO_ROOT_PASSWORD=changeme
# Payload CMS (MITHOME-86) — generálj egyedi, erős titkot ehhez a
# környezethez (pl. `openssl rand -hex 32`); ne ossz meg értéket másik
# environmenttel.
PAYLOAD_SECRET=changeme
# Publikus site URL (beég build időben a Dockerfile-ba) # Publikus site URL (beég build időben a Dockerfile-ba)
NEXT_PUBLIC_SITE_URL=https://stage.mozdit.hu NEXT_PUBLIC_SITE_URL=https://stage.mozdit.hu
+1
View File
@@ -76,6 +76,7 @@ Szükséges változók (`.env` és `proto/.env.local`):
- `NEXT_PUBLIC_WEBMAIL_URL` — Webmail service URL - `NEXT_PUBLIC_WEBMAIL_URL` — Webmail service URL
- `NEXT_PUBLIC_CONTACT_EMAIL` — Kapcsolati email cím - `NEXT_PUBLIC_CONTACT_EMAIL` — Kapcsolati email cím
- `PLANE_API_KEY` — Plane szinkronizációhoz (a `.mcp.json`-ban is lehet) - `PLANE_API_KEY` — Plane szinkronizációhoz (a `.mcp.json`-ban is lehet)
- `PAYLOAD_SECRET` — Payload CMS JWT/session aláíráshoz (MITHOME-86); környezetenként egyedi, erős érték
--- ---
+23
View File
@@ -96,6 +96,26 @@ Next.js 15 alapú weboldal a mozdIT Bt. számára, Docker Compose-szal deployolv
## 📋 Backlog (v1.0.2+) ## 📋 Backlog (v1.0.2+)
### EPIC: Payload CMS migráció (MITHOME-85)
Cél: az ügyfél önállóan, admin felületen szerkeszthesse a tartalmat — a JSON content rendszer (`proto/src/content/pages/*.json`) és a hozzá épült egyedi CMS (`content-editor.js`) lecserélése Payload CMS-re (Next.js App Router-be épülő, MongoDB adapterrel).
| Plane | Feladat | Státusz |
|-------|---------|---------|
| MITHOME-85 | **EPIC**: Áttérés Payload CMS-re (admin felület az ügyfélnek) | 📋 |
| MITHOME-86 | Payload CMS alapinstallálás: config, MongoDB adapter, admin route | ✅ |
| MITHOME-87 | Home/About/Services/Contact/Common Globals kialakítása és JSON tartalom migrálása | ✅ |
| MITHOME-88 | LegalPages collection (adatvedelem, hasznalati-feltetelek) migrálása | 📋 |
| MITHOME-89 | Partners + Media collection: logófeltöltés és transparent-logo funkció migrálása | 📋 |
| MITHOME-90 | Users collection: ügyfél admin bejelentkezés és access control | 📋 |
| MITHOME-91 | Frontend átállítása Payload Local API-ra, JSON content rendszer kivezetése | 📋 |
| MITHOME-92 | Draft/preview és verziózás bekapcsolása (Verziók panel utódja) | 📋 |
| MITHOME-93 | Régi egyedi CMS (content-editor.js) leépítése | 📋 |
| MITHOME-94 | Opcionális: ContactSubmissions collection a Mongoose modell helyett | 📋 |
| MITHOME-95 | .agent/ és CLAUDE.md dokumentáció frissítése Payload architektúrára | 📋 |
| MITHOME-96 | Tesztek migrálása: content schema tesztek helyett Payload collection/global tesztek | 📋 |
| MITHOME-97 | Deploy/Docker frissítés: PAYLOAD_SECRET, admin bundle, Dockerfile/deploy.sh | 📋 |
| Plane | Feladat | Státusz | | Plane | Feladat | Státusz |
|-------|---------|---------| |-------|---------|---------|
| MITHOME-15 | Production domain, Nginx reverse proxy és HTTPS | 📋 | | MITHOME-15 | Production domain, Nginx reverse proxy és HTTPS | 📋 |
@@ -159,6 +179,9 @@ docker-compose -f docker-compose.dev.yml down
--- ---
## Frissítési Napló ## Frissítési Napló
- **2026-09-10**: MITHOME-87 (Home/About/Services/Contact/Common Globals + JSON migráció) kész. Munka közben két, MITHOME-86-hoz visszanyúló hibát javítottunk: (1) a gyökér src/app/layout.tsx ütközött a Payload (payload)/layout.tsx-ével — a webodal saját route-jai átkerültek egy (frontend) route groupba; (2) a kézzel írt importMap.js stub nem volt elég — tsx 4.22.4→4.23.13 (Node 25 kompatibilitás) után legenerálva a valódit. Mindkettő valós böngészőben ellenőrizve (curl nem buktatta fel korábban). `npm run migrate:content` script + `npm test`/lint/build zöld.
- **2026-09-10**: MITHOME-86 (Payload alapinstalláció) kész: Next.js 15.5→16.3.4 upgrade (Payload peer dep miatt), payload.config.ts + mongooseAdapter + minimális Users collection + admin/API/GraphQL route-ok. Élesben ellenőrizve valódi MongoDB-vel. Ismert nyitott advisory a payload@3.88.0-ban (GHSA-jg8r-5jh2-v2xj), nyomon követve MITHOME-90 alatt. Branch: `feature/payload-cms-setup`.
- **2026-09-10**: Payload CMS migrációs EPIC (MITHOME-85) és 12 alfeladat (MITHOME-8697) létrehozva Plane-ben — cél: JSON content + egyedi CMS lecserélése Payload-ra, ügyfél önálló admin szerkesztéshez.
- **2026-08-17**: Plane sync (`plane-sync.js`). Új MITHOME-27 (Production környezet) Backlog-ba; Custom CMS és Webmail bevéve a Plane-be (28/29, Done). `regenerate()` hiba javítva (szekció-elválasztó). - **2026-08-17**: Plane sync (`plane-sync.js`). Új MITHOME-27 (Production környezet) Backlog-ba; Custom CMS és Webmail bevéve a Plane-be (28/29, Done). `regenerate()` hiba javítva (szekció-elválasztó).
- **2026-08-17**: Plane sync futtatása (`plane-sync.js`). MITHOME-15, MITHOME-18 visszaminősítve Backlog-ba (Plane szerint), MITHOME-27/28 megtartva. - **2026-08-17**: Plane sync futtatása (`plane-sync.js`). MITHOME-15, MITHOME-18 visszaminősítve Backlog-ba (Plane szerint), MITHOME-27/28 megtartva.
- **2026-04-26**: Átállás Linear → Plane (MITHOME projekt). TODO.md teljes újraírva, 26 issue szinkronizálva. - **2026-04-26**: Átállás Linear → Plane (MITHOME projekt). TODO.md teljes újraírva, 26 issue szinkronizálva.
+9 -11
View File
@@ -1,16 +1,14 @@
import { dirname } from "path"; // NOTE (Next.js 16 / eslint-config-next 16): the FlatCompat("next/core-web-vitals")
import { fileURLToPath } from "url"; // bridge to the legacy .eslintrc-style config caused a circular-JSON crash in
import { FlatCompat } from "@eslint/eslintrc"; // ESLint 9 (the "react" plugin object closes a cycle when re-validated through
// FlatCompat). eslint-config-next now ships native flat-config arrays, so
const __filename = fileURLToPath(import.meta.url); // import those directly instead of going through the compat layer.
const __dirname = dirname(__filename); import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import nextTypescript from "eslint-config-next/typescript";
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [ const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"), ...nextCoreWebVitals,
...nextTypescript,
{ {
ignores: [ ignores: [
"node_modules/**", "node_modules/**",
+15 -5
View File
@@ -1,13 +1,19 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
import { withPayload } from "@payloadcms/next/withPayload";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
// Enable standalone output for Docker deployment // Enable standalone output for Docker deployment
output: 'standalone', output: 'standalone',
// Skip linting during build for faster Docker builds // NOTE (Next.js 16): `next dev` auto-generates AGENTS.md/CLAUDE.md stub
eslint: { // files describing the framework to AI agents. This project already has
ignoreDuringBuilds: true, // its own agent instruction system (root CLAUDE.md -> .agent/) — a second,
}, // unrelated proto/CLAUDE.md would conflict with it, so this is disabled.
agentRules: false,
// NOTE (Next.js 16): `eslint.ignoreDuringBuilds` was removed — `next build`
// no longer runs ESLint itself (lint is now only `next lint` / `npm run lint`),
// so there is nothing left to ignore here.
// Skip TypeScript checking during build (for faster Docker builds) // Skip TypeScript checking during build (for faster Docker builds)
typescript: { typescript: {
@@ -71,4 +77,8 @@ const nextConfig: NextConfig = {
}, },
}; };
export default nextConfig; // MITHOME-86: bundles the admin UI's route handlers correctly under
// Turbopack/webpack. `@payload-config` itself resolves via the `paths`
// alias in tsconfig.json (./src/payload.config.ts) — this wrapper's
// installed version has no separate configPath option.
export default withPayload(nextConfig);
+4216 -466
View File
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -27,14 +27,21 @@
"docker:dev:down": "cd .. && docker-compose -f docker-compose.dev.yml down", "docker:dev:down": "cd .. && docker-compose -f docker-compose.dev.yml down",
"docker:dev:logs": "cd .. && docker-compose -f docker-compose.dev.yml logs -f app", "docker:dev:logs": "cd .. && docker-compose -f docker-compose.dev.yml logs -f app",
"docker:build": "docker build -t mozdit-app .", "docker:build": "docker build -t mozdit-app .",
"docker:run": "docker run -p 3000:3000 --env-file .env.local mozdit-app" "docker:run": "docker run -p 3000:3000 --env-file .env.local mozdit-app",
"migrate:content": "node --env-file=.env.local --import tsx scripts/migrate-content-to-payload.ts"
}, },
"dependencies": { "dependencies": {
"@payloadcms/db-mongodb": "^3.88.0",
"@payloadcms/next": "^3.88.0",
"@payloadcms/richtext-lexical": "^3.88.0",
"graphql": "^16.14.2",
"mongodb": "^6.5", "mongodb": "^6.5",
"mongoose": "^8.2", "mongoose": "^8.2",
"next": "^15.5.23", "next": "^16.3.4",
"payload": "^3.88.0",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"sharp": "^0.35.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -49,10 +56,11 @@
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/winston": "^2.4", "@types/winston": "^2.4",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.5.2", "eslint-config-next": "^16.3.4",
"jest": "^29.7", "jest": "^29.7",
"jest-environment-jsdom": "^29.7", "jest-environment-jsdom": "^29.7",
"tailwindcss": "^4", "tailwindcss": "^4",
"tsx": "^4.23.13",
"typescript": "^5", "typescript": "^5",
"undici": "^7.15.0", "undici": "^7.15.0",
"winston": "^3.11", "winston": "^3.11",
+104
View File
@@ -0,0 +1,104 @@
/**
* MITHOME-87: egyszeri migrációs script — a proto/src/content/pages/*.json
* + common.json tartalmát átemeli a Payload Globals-ekbe (Home, About,
* Services, Contact, Common) a Local API-n keresztül.
*
* Futtatás (proto/ mappából, futó MongoDB-vel és beállított env-ekkel):
* node --env-file=.env.local --import tsx scripts/migrate-content-to-payload.ts
*
* Idempotens: updateGlobal-t hív, tetszőlegesen többször futtatható —
* mindig a JSON az aktuális "forrás igazság", felülírja a Payload-ban
* lévő korábbi állapotot. NEM törli/nem érinti a JSON fájlokat.
*/
import { getPayload } from 'payload'
import config from '../src/payload.config'
import homeJson from '../src/content/pages/home.json'
import aboutJson from '../src/content/pages/about.json'
import servicesJson from '../src/content/pages/services.json'
import contactJson from '../src/content/pages/contact.json'
import commonJson from '../src/content/common.json'
/** string[] -> [{ value: string }] — lásd src/globals/fields/stringArray.ts */
function toStringArray(items: readonly string[]): { value: string }[] {
return items.map((value) => ({ value }))
}
function buildHomeData(json: typeof homeJson) {
const { hero, about, services, cta, serviceFeatures } = json
return {
hero: {
...hero,
trustBullets: toStringArray(hero.trustBullets),
},
about,
services: {
...services,
items: services.items.map((item) => ({
...item,
features: toStringArray(item.features),
})),
},
cta,
serviceFeatures,
}
}
function buildAboutData(json: typeof aboutJson) {
const { meta, hero, story, mission, team, cta } = json
return {
meta,
hero,
story: { ...story, paragraphs: toStringArray(story.paragraphs) },
mission,
team: { ...team, paragraphs: toStringArray(team.paragraphs) },
cta,
}
}
function buildServicesData(json: typeof servicesJson) {
const { meta, hero, details, support, cta } = json
return {
meta,
hero,
details: {
...details,
services: details.services.map((service) => ({
...service,
specs: {
...service.specs,
items: toStringArray(service.specs.items),
},
})),
},
support,
cta,
}
}
async function run() {
const payload = await getPayload({ config })
await payload.updateGlobal({ slug: 'home', data: buildHomeData(homeJson) })
payload.logger.info('Home global migrálva')
await payload.updateGlobal({ slug: 'about', data: buildAboutData(aboutJson) })
payload.logger.info('About global migrálva')
await payload.updateGlobal({ slug: 'services', data: buildServicesData(servicesJson) })
payload.logger.info('Services global migrálva')
await payload.updateGlobal({ slug: 'contact', data: contactJson })
payload.logger.info('Contact global migrálva')
await payload.updateGlobal({ slug: 'common', data: commonJson })
payload.logger.info('Common global migrálva')
payload.logger.info('MITHOME-87 migráció kész.')
process.exit(0)
}
run().catch((error) => {
console.error('Migráció sikertelen:', error)
process.exit(1)
})
@@ -2,11 +2,11 @@ import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Header from "../components/Header"; import Header from "../../components/Header";
import Footer from "../components/Footer"; import Footer from "../../components/Footer";
import { ThemeProvider } from "../components/ThemeProvider"; import { ThemeProvider } from "../../components/ThemeProvider";
import { siteConfig } from "../config/site"; import { siteConfig } from "../../config/site";
import { common } from "../content"; import { common } from "../../content";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@@ -0,0 +1,17 @@
import type { Metadata } from 'next'
import config from '@payload-config'
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
import { importMap } from '../importMap'
type Args = {
params: Promise<{ segments: string[] }>
searchParams: Promise<{ [key: string]: string | string[] }>
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, params, searchParams, importMap })
export default NotFound
@@ -0,0 +1,16 @@
import type { Metadata } from 'next'
import config from '@payload-config'
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
import { importMap } from '../importMap'
type Args = {
params: Promise<{ segments: string[] }>
searchParams: Promise<{ [key: string]: string | string[] }>
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams, importMap })
export default Page
@@ -0,0 +1,6 @@
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
/** @type import('payload').ImportMap */
export const importMap = {
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
}
@@ -0,0 +1,16 @@
import config from '@payload-config'
import {
REST_DELETE,
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const PUT = REST_PUT(config)
export const OPTIONS = REST_OPTIONS(config)
@@ -0,0 +1,4 @@
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)
@@ -0,0 +1,4 @@
import config from '@payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config)
+29
View File
@@ -0,0 +1,29 @@
import type { ServerFunctionClient } from 'payload'
import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react'
import { importMap } from './admin/importMap'
import '@payloadcms/next/css'
type Args = {
children: React.ReactNode
}
const serverFunction: ServerFunctionClient = async function (args) {
'use server'
return handleServerFunctions({
...args,
config,
importMap,
})
}
const Layout = ({ children }: Args) => (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
</RootLayout>
)
export default Layout
+15
View File
@@ -0,0 +1,15 @@
import type { CollectionConfig } from 'payload'
/**
* Admin bejelentkezés (MITHOME-86 bootstrap). A teljes access control /
* jelszó-politika finomítása MITHOME-90 feladata — itt csak a Payload
* indulásához szükséges minimális auth collection.
*/
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
fields: [],
}
+9
View File
@@ -38,6 +38,13 @@ export function ThemeProvider({ children, defaultTheme = 'system' }: ThemeProvid
const [mounted, setMounted] = useState(false) const [mounted, setMounted] = useState(false)
useEffect(() => { useEffect(() => {
// WHY: this effect synchronizes React state with two external systems —
// the DOM (hydration-safe mount flag) and localStorage (persisted theme
// preference) — read once on mount. There is no subscription to wrap the
// setState calls in, so the new react-hooks/set-state-in-effect rule
// (added with the Next.js 16 / eslint-config-next upgrade) is a false
// positive here; suppress rather than restructure a working component.
// eslint-disable-next-line react-hooks/set-state-in-effect
setMounted(true) setMounted(true)
const savedTheme = localStorage.getItem('theme') as Theme | null const savedTheme = localStorage.getItem('theme') as Theme | null
if (savedTheme) { if (savedTheme) {
@@ -59,6 +66,8 @@ export function ThemeProvider({ children, defaultTheme = 'system' }: ThemeProvid
root.setAttribute('data-theme', theme) root.setAttribute('data-theme', theme)
} }
// WHY: syncs React state with the resolved DOM/OS theme — see note above.
// eslint-disable-next-line react-hooks/set-state-in-effect
setResolvedTheme(resolved) setResolvedTheme(resolved)
}, [theme, mounted]) }, [theme, mounted])
+73
View File
@@ -0,0 +1,73 @@
import type { GlobalConfig } from 'payload'
import { stringArrayField } from './fields/stringArray'
/** Mirrors AboutPageContent (proto/src/content/types.ts) — MITHOME-87. */
export const About: GlobalConfig = {
slug: 'about',
admin: {
group: 'Oldalak',
},
fields: [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{ name: 'ogDescription', type: 'textarea', required: true },
],
},
{
name: 'hero',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
],
},
{
name: 'story',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
stringArrayField('paragraphs', 'Paragraphs'),
],
},
{
name: 'mission',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
{
name: 'values',
type: 'array',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{ name: 'icon', type: 'text', required: true },
],
},
],
},
{
name: 'team',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
stringArrayField('paragraphs', 'Paragraphs'),
],
},
{
name: 'cta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'textarea', required: true },
{ name: 'button', type: 'text', required: true },
],
},
],
}
+73
View File
@@ -0,0 +1,73 @@
import type { GlobalConfig } from 'payload'
/** Mirrors CommonContent (proto/src/content/types.ts) — MITHOME-87. */
export const Common: GlobalConfig = {
slug: 'common',
admin: {
group: 'Oldalak',
},
fields: [
{
name: 'buttons',
type: 'group',
fields: [
{ name: 'contact', type: 'text', required: true },
{ name: 'learnMore', type: 'text', required: true },
{ name: 'webmail', type: 'text', required: true },
{ name: 'sendMessage', type: 'text', required: true },
],
},
{
name: 'labels',
type: 'group',
fields: [
{ name: 'required', type: 'text', required: true },
{ name: 'features', type: 'text', required: true },
],
},
{
name: 'validation',
type: 'group',
fields: [
{ name: 'required', type: 'text', required: true },
{ name: 'invalidEmail', type: 'text', required: true },
{
name: 'minLength',
type: 'text',
required: true,
admin: {
description: 'A {min} placeholder futásidőben cserélődik ki.',
},
},
],
},
{
name: 'staging',
type: 'group',
fields: [{ name: 'banner', type: 'text', required: true }],
},
{
name: 'footer',
type: 'group',
fields: [
{
name: 'copyright',
type: 'text',
required: true,
admin: {
description: 'A {year} placeholder futásidőben cserélődik ki.',
},
},
{ name: 'address', type: 'text', required: true },
],
},
{
name: 'a11y',
type: 'group',
fields: [
{ name: 'openMenu', type: 'text', required: true },
{ name: 'closeMenu', type: 'text', required: true },
],
},
],
}
+142
View File
@@ -0,0 +1,142 @@
import type { GlobalConfig } from 'payload'
/**
* Mirrors ContactPageContent (proto/src/content/types.ts) — MITHOME-87.
*
* WHY `gdpr.label` textarea és nem richText: a JSON forrás egy kézzel
* beírt <a> taget tartalmazó HTML string (lásd contact.json). A lexical
* richText mező más (JSON node-fa) szerializációt használna, ami extra
* migrációs/render logikát igényelne — ezt itt, bootstrap szinten nem
* bontjuk ki, marad egyszerű (HTML-t tartalmazó) szöveg mező.
*/
export const Contact: GlobalConfig = {
slug: 'contact',
admin: {
group: 'Oldalak',
},
fields: [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
],
},
{
name: 'hero',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
],
},
{
name: 'form',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'successMessage', type: 'text', required: true },
{ name: 'errorMessage', type: 'text', required: true },
{
name: 'fields',
type: 'group',
fields: [
{
name: 'name',
type: 'group',
fields: [
{ name: 'label', type: 'text', required: true },
{ name: 'placeholder', type: 'text', required: true },
{ name: 'error', type: 'text', required: true },
],
},
{
name: 'email',
type: 'group',
fields: [
{ name: 'label', type: 'text', required: true },
{ name: 'placeholder', type: 'text', required: true },
{ name: 'errorRequired', type: 'text', required: true },
{ name: 'errorInvalid', type: 'text', required: true },
],
},
{
name: 'subject',
type: 'group',
fields: [
{ name: 'label', type: 'text', required: true },
{ name: 'placeholder', type: 'text', required: true },
{ name: 'error', type: 'text', required: true },
],
},
{
name: 'message',
type: 'group',
fields: [
{ name: 'label', type: 'text', required: true },
{ name: 'placeholder', type: 'text', required: true },
{ name: 'errorRequired', type: 'text', required: true },
{ name: 'errorMinLength', type: 'text', required: true },
],
},
{
name: 'gdpr',
type: 'group',
fields: [
{ name: 'label', type: 'textarea', required: true },
{ name: 'error', type: 'text', required: true },
],
},
],
},
{ name: 'submitButton', type: 'text', required: true },
{ name: 'submittingButton', type: 'text', required: true },
],
},
{
name: 'info',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{
name: 'email',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'responseTime', type: 'text', required: true },
],
},
{
name: 'company',
type: 'group',
fields: [{ name: 'title', type: 'text', required: true }],
},
{
name: 'webmail',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'linkText', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
],
},
],
},
{
name: 'faq',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{
name: 'items',
type: 'array',
fields: [
{ name: 'question', type: 'text', required: true },
{ name: 'answer', type: 'textarea', required: true },
],
},
],
},
],
}
+105
View File
@@ -0,0 +1,105 @@
import type { GlobalConfig } from 'payload'
import { stringArrayField } from './fields/stringArray'
/**
* Mirrors HomePageContent (proto/src/content/types.ts) — MITHOME-87.
*
* WHY nincs `partners` mező: a home.partners.items a JSON-ban ma egy
* beágyazott lista, de a Payload oldalon önálló `Partners` collection lesz
* (logó = Media upload reference) — lásd MITHOME-89. Itt szándékosan
* kihagyjuk, nehogy két, egymásnak ellentmondó forrás legyen.
*/
export const Home: GlobalConfig = {
slug: 'home',
admin: {
group: 'Oldalak',
},
fields: [
{
name: 'hero',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
stringArrayField('trustBullets', 'Trust bullets'),
{
name: 'cta',
type: 'group',
fields: [
{
name: 'primary',
type: 'group',
fields: [
{ name: 'text', type: 'text', required: true },
{ name: 'href', type: 'text', required: true },
{ name: 'external', type: 'checkbox', defaultValue: false },
],
},
{
name: 'secondary',
type: 'group',
fields: [
{ name: 'text', type: 'text', required: true },
{ name: 'href', type: 'text', required: true },
{ name: 'external', type: 'checkbox', defaultValue: false },
],
},
],
},
],
},
{
name: 'about',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{
name: 'usps',
type: 'array',
fields: [
{ name: 'id', type: 'text', required: true },
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{ name: 'icon', type: 'text', required: true },
],
},
],
},
{
name: 'services',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
{
name: 'items',
type: 'array',
fields: [
{ name: 'id', type: 'text', required: true },
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{ name: 'icon', type: 'text', required: true },
stringArrayField('features', 'Features'),
{ name: 'ctaText', type: 'text', required: true },
],
},
],
},
{
name: 'cta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'textarea', required: true },
{ name: 'button', type: 'text', required: true },
],
},
{
name: 'serviceFeatures',
type: 'group',
fields: [{ name: 'title', type: 'text', required: true }],
},
],
}
+82
View File
@@ -0,0 +1,82 @@
import type { GlobalConfig } from 'payload'
import { stringArrayField } from './fields/stringArray'
/** Mirrors ServicesPageContent (proto/src/content/types.ts) — MITHOME-87. */
export const Services: GlobalConfig = {
slug: 'services',
admin: {
group: 'Oldalak',
},
fields: [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{ name: 'ogDescription', type: 'textarea', required: true },
],
},
{
name: 'hero',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
],
},
{
name: 'details',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
{
name: 'services',
type: 'array',
fields: [
{ name: 'icon', type: 'text', required: true },
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'textarea', required: true },
{
name: 'specs',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
stringArrayField('items', 'Items'),
],
},
],
},
],
},
{
name: 'support',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'text', required: true },
{
name: 'channels',
type: 'array',
fields: [
{ name: 'icon', type: 'text', required: true },
{ name: 'title', type: 'text', required: true },
{ name: 'description', type: 'text', required: true },
],
},
],
},
{
name: 'cta',
type: 'group',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'subtitle', type: 'textarea', required: true },
{ name: 'primaryButton', type: 'text', required: true },
{ name: 'secondaryButton', type: 'text', required: true },
],
},
],
}
+24
View File
@@ -0,0 +1,24 @@
import type { ArrayField } from 'payload'
/**
* Payload has no native "array of plain strings" field — the closest
* built-in shape is an array of one-field objects. Used for every
* string[] in the old content/types.ts (trustBullets, paragraphs,
* features, spec items, …) so the JSON -> Payload migration script has a
* single, consistent shape to transform into.
*/
export function stringArrayField(name: string, label?: string, required = false): ArrayField {
return {
name,
type: 'array',
label,
fields: [
{
name: 'value',
type: 'text',
required: true,
},
],
required,
}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Payload CMS configuration (MITHOME-86 — alapinstalláció).
*
* WHY a saját MongoDB kapcsolatot használjuk: a projekt már MongoDB-t futtat
* (proto/src/lib/mongodb.ts) — Payload a `mongooseAdapter`-en keresztül
* ugyanabba az adatbázisba ír, nincs szükség külön DB-re/szerverre.
*
* MITHOME-87: Home/About/Services/Contact/Common Globals hozzáadva — ezek
* tükrözik a proto/src/content/pages/*.json + common.json struktúráját.
* A LegalPages/Partners/Media collectionök a MITHOME-88..89-ben kerülnek be.
*/
import path from 'path'
import { fileURLToPath } from 'url'
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import sharp from 'sharp'
import { Users } from './collections/Users'
import { Home } from './globals/Home'
import { About } from './globals/About'
import { Services } from './globals/Services'
import { Contact } from './globals/Contact'
import { Common } from './globals/Common'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
// Admin felület — bejelentkezés az Users collection-nel (MITHOME-90-ben
// finomodik: access control, jelszó-politika stb.)
admin: {
user: Users.slug,
},
editor: lexicalEditor(),
collections: [Users],
globals: [Home, About, Services, Contact, Common],
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: mongooseAdapter({
url: process.env.MONGODB_URI || '',
}),
sharp,
})
+22 -5
View File
@@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@@ -11,7 +15,7 @@
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
@@ -19,9 +23,22 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
],
"@payload-config": [
"./src/payload.config.ts"
]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }