Skip to content

About

Next.js event platform frontend with CMS-style content, dynamic routes, i18n, and motion.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

41 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

WebVoltz

🎭 StageGrid Events

A sample event/artist ticketing frontend built on Next.js 14 (App Router), TypeScript, Redux Toolkit, and a headless-CMS-shaped content model: browse shows on the home page, drill into an artist's page to pick a venue/date/time, or land on a venue page grouped by its upcoming shows. It exists to show the pattern end to end - CMS-style content envelopes, dynamic slug-based routing, i18n, and GSAP-driven, media-heavy layouts - rather than to be a product in its own right.

This is a portfolio/demo repository. There is no private backend: every "CMS" call is served locally by a small mock client (src/lib/cms/) shaped exactly like a real headless CMS response, so the app runs fully standalone with zero configuration.

✨ Features

  • CMS-style content modeling - fixture data is reshaped into the same { data: { id, attributes } } envelope a real headless CMS (e.g. Strapi) returns, including relations and media fields, so swapping in a real backend touches one file (src/lib/cms/client.ts), not the components.
  • Dynamic, ambiguous-slug routing - artists and venues share the same top-level slug space (/[artistSlug]); the venue route resolves which one a given slug means and renders accordingly, a realistic constraint for a CMS where editors pick slugs independently.
  • An interactive ticket picker - venue, date, and time selection (ShowDateTime) with an active-state reveal animation, deep-linkable via ?activeEvent=.
  • Full i18n, RTL by default - i18next with Hebrew (default, RTL) and English locales; the document renders dir="rtl" out of the box.
  • GSAP-driven motion - scroll-triggered reveals, a page-load sequence, and hover interactions, without any custom window.innerWidth/resize-listener logic.
  • Resilient media - every image goes through next/image, including a fallback component that swaps to a local "image not found" asset on load error, so a broken CMS media URL never breaks layout.
  • Explicit error and empty states - the CMS client exposes a test-only simulated-failure hook so the rejected-thunk path is exercised for real, not just assumed to work.
  • Strict TypeScript - the full strict compiler family plus exactOptionalPropertyTypes/noUncheckedIndexedAccess, zero-warning ESLint.

πŸ“Έ Screenshots

All captured from the app running locally against the mock CMS layer (npm run dev).

Home

Home page hero and first show card

The hero banner (rotates through featured shows on each load), followed by the categorized show rows.

Artist page

Artist detail page with the ticket picker

An artist's hero plus the interactive venue/date/time ticket picker.

Venue page

Venue page hero

A venue page, resolved from the same /[artistSlug] route segment artists use.

Search

Live search suggestion while typing

The full-screen search overlay, showing a live suggestion as the query is typed.

Mobile

Home page on a mobile viewport

The home page at a 390px viewport - single-column cards, RTL-aware stacking.

🧰 Tech stack

Technology Role
Next.js 14 (App Router) Routing, Server Components, rendering
TypeScript 5 (strict) Type safety
Redux Toolkit Application state, createAsyncThunk-backed slices
GSAP + ScrollTrigger Scroll-triggered and page-load animation
Swiper Show/venue carousels and the ticket date/time picker
Ant Design Form primitives (search input, newsletter form)
i18next / react-i18next Internationalization (Hebrew default, English fallback)
dayjs Date formatting and locale-aware day names
cheerio Stripping HTML from CMS-style rich-text fields for card previews
Jest + React Testing Library Unit and integration tests
ESLint, Prettier, Husky, commitlint, Gitleaks Code quality and security gates

πŸ—οΈ Architecture

flowchart LR
    UI["React UI\n(app/, components/)"]
    RTK["Redux Toolkit slice\n(createAsyncThunk)"]
    Client["CMS client\n(src/lib/cms/client.ts)"]
    Shapes["Envelope shaping\n(src/lib/cms/shapes.ts)"]
    Fixtures["Fixture data\n(src/lib/cms/fixtures)"]

    UI -->|dispatch thunk, or await directly in a Server Component| RTK
    RTK --> Client
    UI -->|Server Components call it directly| Client
    Client --> Shapes
    Shapes --> Fixtures
    Fixtures -->|CMS-shaped envelope| Client
    Client -->|typed result| RTK
    RTK -->|selector| UI
Loading

Server Components (the route files themselves) call src/lib/cms/client.ts directly and await the result during render, rather than dispatching through Redux - the store is a client-side singleton, and Next.js serves concurrent requests on the same process, so a Server Component dispatch could race another request's dispatch. Client components that need CMS data after the initial render (Header, Footer, ArtistTickets) go through the Redux slices instead. Either path ends at the same client.ts functions, so swapping fixtures for a real CMS is a one-file change (see Mock CMS layer).

πŸ“ Project structure

src/
  app/                    Next.js App Router routes - see the route map below
  components/             Route-level and shared UI components
  redux/                  Redux Toolkit store and slices
  lib/cms/                Mock CMS client + fixtures + envelope shaping (see below)
  i18n/                   i18next config and locale resources
  assets/                 Icons and images bundled with the app
  helpers/, utils/        Small framework-agnostic utilities
public/images/fixtures/   Placeholder artwork referenced by the CMS fixtures
docs/screenshots/         README screenshots

Route map

Route File Purpose
/ src/app/(HomeLayout)/page.tsx Home: hero banner, categorized show rows, newsletter CTA
/[artistSlug] src/app/[artistSlug]/(venue)/page.tsx Venue page for that slug, or falls back to the artist at that slug (see below)
/[artistSlug]/[artistId] src/app/[artistSlug]/[artistId]/page.tsx A specific artist + show: hero, ticket/date picker

(venue) is a route group - it does not appear in the URL. It exists so /[artistSlug] and /[artistSlug]/[artistId] can be sibling segments under the same dynamic parent without conflicting.

πŸ”„ Data flow

Resolving an ambiguous /[artistSlug]:

Venue route receives :artistSlug
  -> queries both the venue collection and the artist collection for that slug
  -> neither matches -> notFound()
  -> an artist matches -> renders the artist page's component directly
     -> that component redirects to the artist's next show, /[artistSlug]/[artistId]
  -> otherwise -> treated as a venue, its grouped show listing renders

Loading the artist detail route:

/[artistSlug]/[artistId] Server Component
  -> await cms.getAllArtistData(artistSlug), await cms.getArtistShowsDetail(artistId)
  -> either missing -> notFound()
  -> ShowDateTime renders the venue/date/time picker from the resolved show data
  -> selecting a slot updates the URL (?activeEvent=...) so the picked slot is deep-linkable

πŸ”‘ Environment variables

See .env.example. The only variable the app reads is NEXT_PUBLIC_IMAGE_URL, an optional prefix for CMS-hosted media URLs - it's unused while running against the local fixtures, and only becomes relevant once client.ts is pointed at a real CMS.

🎭 Mock CMS layer

src/lib/cms/ stands in for a real headless CMS:

  • fixtures/entities.ts - a small, flat "database" of artists, venues, and shows. This is the only file you'd touch to change the sample content.
  • shapes.ts - reshapes that flat data into the same { data: { id, attributes } } envelope a real headless CMS returns, including relations (Artists: { data: [...] }) and media fields ({ data: { attributes: { url } } }).
  • client.ts - one async function per endpoint the app uses (getSettings, getAllArtistData, getVenueShows, etc.), each resolving fixture data with a simulated network delay. It also exposes __setSimulatedFailure(), a test-only hook used to exercise error states without a real network mock.

To point this at a real CMS, replace the bodies of the functions in client.ts with fetch/axios calls against your CMS's API - the envelope shapes, and everything that consumes them (components, Redux slices), stay the same.

🌐 Internationalization

src/i18n/index.ts configures i18next with two locales, he (default, RTL) and en, loaded from src/i18n/locales/<lng>/home.json. The app renders RTL by default (<html lang="he" dir="rtl"> in src/app/layout.tsx).

To add a locale: create src/i18n/locales/<lng>/home.json, add it to the resources map and supportedLngs array in src/i18n/index.ts, and add its locale code to next-i18next.config.mjs.

🎬 Animation & performance

  • GSAP drives page and scroll-triggered animation (gsap.registerPlugin(ScrollTrigger) in Header/Footer), plus a page loader sequence in components/Layout.
  • Fonts load via next/font/google (Heebo for Hebrew + Latin body copy, Oswald for condensed headings) in src/app/layout.tsx. Both are self-hosted by Next at build time - no runtime requests to Google Fonts, and no font files checked into the repo.
  • Images go through next/image everywhere, including a small FallBackImg component (src/components/common/Image/FallBackImg.tsx) that swaps to a local "image not found" asset on load error.
  • Video is optional and data-driven - the hero banner plays a video when the current fixture show has one (Show_Video), and falls back to a poster image when it doesn't - the same branch a real CMS-backed deployment with actual show footage would use.

πŸ’» Local development

Requires the Node version pinned in .nvmrc, plus Gitleaks 8.30.x on PATH before your first commit

  • the pre-commit hook refuses to run without it.
npm install
npm run prepare   # installs the Husky git hooks (also runs automatically after npm install)
npm run dev

Open http://localhost:3000. No environment variables are required to run, build, or test the app - see Environment variables above.

βš™οΈ Available commands

Command Purpose
npm run dev Start the Next.js dev server.
npm run build Type-checked production build (no ignoreBuildErrors escape hatch).
npm start Serve the production build.
npm run quality format:check + lint + typecheck, all zero-tolerance (what the pre-commit hook runs).
npm run format / format:check Prettier write / check.
npm run lint ESLint, flat config, --max-warnings=0.
npm run typecheck tsc --noEmit.
npm test Jest + React Testing Library.
npm run security:audit npm audit --audit-level=high.

πŸ§ͺ Testing

npm test

Jest (via next/jest) + React Testing Library, covering:

  • dynamic route data resolution from fixtures (known + unknown slugs/ids)
  • i18n key lookup across locales, including fallback behavior
  • the missing-image fallback path
  • the CMS client's simulated-failure / rejected-thunk path

πŸ”’ Code quality and security

This repo's tooling is adapted from Webvoltz's internal Engineering Standards, with the runtime deliberately left on Next.js 14 / React 18 / TypeScript 5 and CI kept on GitHub Actions rather than the standard's GitLab pipeline - both were already in place before this pass and weren't worth the risk of an unrelated major-version bump.

A Husky pre-commit hook runs Gitleaks secret scanning, lint-staged, the full quality check, and a production build before any commit is allowed through; commit-msg enforces Conventional Commits via commitlint. The same gates run in CI (.github/workflows/ci.yml) - secret scan and dependency audit first, then quality and commit-message lint, then tests, then the production build.

TypeScript strictness: every flag from the standard's strict preset is on - noUncheckedIndexedAccess, exactOptionalPropertyTypes, noPropertyAccessFromIndexSignature, noUnusedLocals/noUnusedParameters, noImplicitReturns, noImplicitOverride, and friends - and next.config.mjs's typescript.ignoreBuildErrors escape hatch (present in the original codebase) is gone; next build now type-checks for real.

One deliberate exception: skipLibCheck stays true. Turning it off surfaces type declaration bugs in third-party packages this repo depends on (@reduxjs/toolkit, antd, even next itself) once exactOptionalPropertyTypes is on - none of which are fixable without editing node_modules. Documented here rather than silently dropped.

Tracked, not done in this pass:

  • @typescript-eslint/no-explicit-any is off. This codebase inherited roughly 85 : any call sites from before this cleanup; turning the rule on today would fail --max-warnings=0 across the whole repo. Everything else the standard requires (no-unsafe-*, no-floating-promises, exhaustive switches, etc.) is a larger, separate pass once those call sites are typed properly.
  • The runtime major-version bump (Next 16 / React 19 / TypeScript 6), called for by the standard, is out of scope here - it'd be a separate migration with its own review, not a drive-by tooling change.
  • GitLab CI. This repo lives on GitHub; the standard's GitLab quality jobs (secret-scan, dependency-audit, quality, commitlint, test, build) are reproduced as GitHub Actions jobs in .github/workflows/ci.yml instead of being ported as-is.

🧠 Design decisions

  • Server Components call the CMS client directly instead of dispatching Redux - src/redux/store.ts is a client-side singleton; Next.js serves concurrent requests (including the browser's own <Link> prefetch racing a real navigation) on the same process, so a Server Component dispatch could read back another request's state. Routes await cms.*() directly instead; only client components that need CMS data post-render go through Redux.
  • CMS-shaped fixtures instead of ad hoc mock data - src/lib/cms/shapes.ts reshapes flat fixture data into the same envelope a real headless CMS returns, so the components, Redux slices, and TypeScript interfaces are all written against the shape a real integration would actually produce - swapping the mock for a live CMS doesn't ripple through the codebase.
  • A shared IMAGE_BASE_URL constant, never a bare template string - every CMS-hosted image URL is built as `${IMAGE_BASE_URL}${path}`; IMAGE_BASE_URL falls back to '' so fixture-relative paths resolve correctly when NEXT_PUBLIC_IMAGE_URL isn't set, instead of rendering the literal string "undefined" in front of every image URL.
  • Decorative placeholder art renders its label from i18next, not baked into the image - the gift-card and gallery placeholder graphics are inline SVG React components (src/components/common/PlaceholderArt) that pull their caption from the current locale, so a purely decorative label still follows the site's language instead of being frozen in whichever language the asset happened to be generated in.
  • skipLibCheck: false-equivalent rigor, with one documented exception - see Code quality and security.

πŸ› Troubleshooting

  • A ticket card's heading/date never appears - some of this app's reveal animations are gated on an active class that has to be applied by the component using them (see the fix in ShowDateTime); a component that renders .text_box_wrap without ever adding active gets stuck in the CSS's permanently-hidden end state.
  • An image renders full-bleed instead of its intended size - check for a same-named CSS class (e.g. .image_box_wrap) reused by a different component elsewhere in the same page bundle; Next.js bundles all component CSS per route, so two components sharing a generic class name can leak styles into each other even though neither one is scoped incorrectly on its own.
  • Pre-commit hook fails immediately - it refuses to run without Gitleaks on PATH (gitleaks version should print 8.30.x); see Code quality and security.
  • A CMS URL renders the literal text "undefined" - NEXT_PUBLIC_IMAGE_URL isn't set and the code path building the URL isn't using the shared IMAGE_BASE_URL constant (see Design decisions).

πŸš€ Future improvements

  • A shows/tickets listing on the venue page itself (currently hero-only; grouped show data is fetched but not yet rendered there).
  • A real gallery section wired up to the artist gallery fixture data (GalleryPlaceholder exists but has no current consumer).
  • Real backend integration guide once a production headless CMS instance exists.
  • Checkout/payment flow beyond the ticket picker.

πŸ“„ License

Portfolio/demonstration repository - no license file is included.

About

Next.js event platform frontend with CMS-style content, dynamic routes, i18n, and motion.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages