## What Adds GA4 to `artcraft-website-next`, covering the whole marketing site. - `src/components/analytics.tsx` loads gtag.js (`afterInteractive`) and installs one delegated click listener. It's mounted once in the root layout. - `src/lib/analytics.ts` holds the typed event catalogue, `trackEvent()`, `trackAttrs()` (data attributes that work in server components) and the link classifier. - Analytics is **off unless `NEXT_PUBLIC_GA_MEASUREMENT_ID` is set**, so local dev and deploy previews stay out of the reports. ## Why The new site had no analytics. This gives us download, signup and checkout funnels, plus CTA performance by page section. ## Events GA4 Enhanced Measurement handles page views (App Router history changes), scrolls, outbound clicks and common file downloads. On top of that: | Event | Trigger | |---|---| | `app_download` | GitHub release asset clicks (ArtCraft + Crafting Apps): app, version, platform, file | | `webapp_click` | Links to app.getartcraft.com: destination path | | `cta_click` | Other `Button` links (tagged `data-cta`) | | `app_select` | App cards and icons on `/apps` and sibling cards on app pages: app name | | `social_click`, `contact_click` | `SOCIAL_LINKS` hosts, mailto / copy email | | `begin_checkout` | Plan selection (`signup`/`subscribe`/`switch`) and credit packs, with GA4 ecommerce `items` | | `billing_cadence_change`, `view_credit_packs`, `manage_subscription` | Pricing actions | | `prompt_submit`, `video_generation` | H3 promptbox (`logged_in`; complete/failed/timeout) | | `sign_up`, `login` | Auth gate success | | `generate_lead` | Beta form submission (honeypot hits excluded) | | `video_start`, `share`, `copy_command`, `copy_prompt`, `media_download`, `press_kit_download` | Video facades, share bars, copy buttons, media/press downloads | Every delegated click event also carries `link_location`: nav, footer, modal, or the enclosing `section[id]`. ## Reviewer notes - `Button` now renders `data-cta=""` on its anchor variants. There is no visual change. - `PricingTable.choosePlan` now branches on the computed `checkoutType` rather than repeating the `user`/`hasActivePlan` conditions. The behaviour is unchanged. - `AuthGateModal` forms now return early on error instead of using if/else, so the success event goes on the success path. ## Deploy Set `NEXT_PUBLIC_GA_MEASUREMENT_ID` in Netlify, **Production context only**, then redeploy. The ID is inlined at build time. This is documented in `netlify.toml`. After deploying: - Mark key events in GA: `app_download`, `begin_checkout`, `sign_up`, `generate_lead`, `webapp_click`. - Register custom dimensions (e.g. `link_location`, `app_name`, `platform`, `destination`, `checkout_type`) so the params show in reports. ## Testing Ran the dev server with a dummy measurement ID and checked `dataLayer` on these pages: - `/`: nav, footer, CTA, social, mailto and YouTube events. - `/download`: Windows and macOS installers. - `/apps/photocraft`: Linux downloads, share, copy command, GitHub link, and `app_select` on the sibling cards (also checked on the `/apps` icon row and lineup cards). - `/pricing`: cadence toggle. - `/minimax-h3`: logged-out `prompt_submit` and example `video_start`. Not exercised in the browser: the beta form (submitting would post a real Google Form entry), signup/login, checkout, the credits modal, the media page, the press kit, email copy and manage plan. `tsc` shows no new errors. Existing errors in `media-viewer-3d.tsx` and the Playwright test files are unchanged. The production build was not run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Frontend
This is the Nx workspace for ArtCraft web apps and shared libraries. The desktop frontend is maintained in storytold/artcraft.
All commands to run these projects are performed from this directory (except the repo-root launcher scripts noted below).
Requirements
- Node.js 20+ (Nx 21 / Vite 6 requirement). Install via nvm,
nodejs.org, or
brew install node. - npm (ships with Node).
We use npm, not pnpm. pnpm was briefly adopted ("add pnpm to solve build issues") and later removed ("fix(frontend): consume video-editor libs via nx path aliases, drop pnpm"). If you have leftovers from that era — a
pnpm-lock.yaml,pnpm-workspace.yaml, or anode_modules/.pnpmdirectory — npm installs will fail (typically withENOTEMPTYrename errors). See Troubleshooting below.
Install dependencies
npm install
Running the apps in dev mode
From the repository root, the launcher scripts (these preflight your environment, install dependencies, and free the dev port first):
./script/website/unix_frontend_website_dev.sh # artcraft-website (marketing site), port 4200
./script/website/unix_frontend_webapp_dev.sh # artcraft-webapp (user dashboard), port 4201
Run the webapp from artcraft-services, not the separate artcraft desktop
checkout. The webapp launcher also works from another directory when invoked
by its full or relative path:
../artcraft-services/script/website/unix_frontend_webapp_dev.sh
Or directly from this directory:
nx dev artcraft-website
npx nx dev @frontend/artcraft-webapp
Building
npx nx build artcraft-website
npx nx build artcraft-webapp
Netlify deploys run apps/<app>/script/netlify_build.sh (see each app's
netlify.toml).
Troubleshooting
npm install fails with ENOTEMPTY: directory not empty, rename ...
Your node_modules tree is corrupted — usually an interrupted install, or a
tree written by pnpm back when the repo briefly used it. Fix:
./clean_modules.sh
npm install
Or the manual minimum:
rm -rf node_modules .nx pnpm-lock.yaml pnpm-workspace.yaml
npm install
Stale build state after switching branches
Call this when starting on main or a new branch:
./clean_modules.sh
It removes all dist/ outputs, resets the nx cache, and removes
node_modules.
Import aliases
The names for @frontend and @storyteller come from the package.json file
in the libs folder:
import { Login } from "@frontend/login";
import { api } from "@storyteller/api";
For shared UI components, import from @storyteller/ui-[componentname]:
import { Button } from "@storyteller/ui-button";
import { Modal } from "@storyteller/ui-modal";
Generating a new component library
# 1. Generate the library
npx nx g @nx/react:library libs/components/toaster --import-path=@storyteller/ui-toaster --bundler=vite
npm install
# 2. Import it somewhere in code
# 3. Sync and build
nx sync
nx build