-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path.cursorrules
More file actions
252 lines (208 loc) · 13.6 KB
/
Copy path.cursorrules
File metadata and controls
252 lines (208 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# EMC – Event Management Console
## Project Identity
EMC is an **Adobe Experience Cloud (ExC) Shell** application built on **Adobe App Builder**.
It is a single-page application (SPA) for managing events, series, speakers, sponsors,
venues, and attendees within the Adobe ecosystem. There is **no backend in this repo**;
the frontend calls external Adobe APIs (ESP and ESL) directly.
---
## Tech Stack (code-validated)
| Layer | Technology |
| ------------------ | -------------------------------------------------- |
| Framework | React 18 (hooks + functional components; no class components — required for Spectrum 2) |
| Language | TypeScript (strict, target ES2020, bundler module) |
| UI Library | **React Spectrum 2** (`@react-spectrum/s2`) only |
| Icons | `@react-spectrum/s2/icons/...` |
| Routing | React Router 6 (`react-router-dom`, HashRouter) |
| State Management | React Context + `useReducer` (no Redux/Zustand) |
| Rich Text | Quill 2 |
| Date/Time | `@internationalized/date` |
| Build | Adobe I/O CLI (`aio`) with Parcel |
| Testing | Jest 29 (configured, no test files yet) |
| Linting | ESLint 8 (`eslint:recommended`, `plugin:jest`) |
| Node | >= 18 |
| Package Manager | npm (lockfile v3) |
### Important: React Spectrum version
**Use React Spectrum 2** from `@react-spectrum/s2` (style macro, S2 icons, S2 docs). `@adobe/react-spectrum` is not a dependency. Cursor rules: prioritize `react-spectrum-s2.mdc`; `react-spectrum-v3.mdc` is historical reference for migration terminology.
---
## Project Structure (code-validated)
```
EMC/
├── app.config.yaml # Adobe App Builder config (web + ExC extension)
├── package.json # Scripts: dev, dev:local, lint, lint:fix, type-check
├── tsconfig.json # TypeScript config (strict, ES2020, bundler)
├── .eslintrc.json # ESLint config
├── .babelrc # Babel (JSX transform)
├── jest.setup.js # Jest timeout setup
│
├── web-src/ # ★ Frontend root
│ ├── index.html # SPA entry point (#root)
│ └── src/
│ ├── index.tsx # React bootstrap (ExC Shell + standalone)
│ ├── index.css # Global styles (nav, tables, panels)
│ │
│ ├── components/
│ │ ├── App.tsx # Root component: Provider, Router, Grid layout
│ │ ├── layout/ # TopNav
│ │ ├── shared/ # Reusable: DataTable, FormWizard, FormCard,
│ │ │ # StatusBadge, LoadingSpinner, ImageUploader,
│ │ │ # RichTextEditor, TagSelector, AutocompleteTextField,
│ │ │ # ToastContainer, ResourceDashboardLayout,
│ │ │ # HistoryTimeline, HeadingWithTooltip, etc.
│ │ ├── dev/ # DevTokenButton, DevTokenDialog
│ │ └── user/ # UserPanel
│ │
│ ├── pages/ # ★ Route-level page components
│ │ ├── AttendeeDashboard/
│ │ ├── CloudManagementConsole/
│ │ ├── EventForm/ # Multi-step wizard (14 sub-components)
│ │ ├── EventsDashboard/
│ │ ├── OverviewDashboard/
│ │ ├── SeriesDashboard/
│ │ ├── SeriesForm/
│ │ └── SpeakersDashboard/
│ │
│ ├── contexts/ # ApiContext, EventFormContext, SeriesFormContext, ToastContext
│ ├── hooks/ # useLoadData, useDevToken, useEventFormComponent,
│ │ # useEventFormSave, useSeriesFormComponent,
│ │ # useRsvpConfig, useEventTypeFeatures, useSafeState
│ ├── services/ # api.ts, cacheUtils, requestHelpers, payloadBuilders,
│ │ # tokenStorage, configService, dataEnrichment,
│ │ # seriesEnrichment, eventEnrichment
│ ├── config/ # constants.ts, env.ts, localeMapping, eventTypeConfig, externalConfigs
│ ├── types/ # domain.ts, runtime.ts, attendee.ts, google-places.d.ts
│ ├── utils/ # dataFilters, deduplication, eventFormMappers, formPersistence,
│ │ # loadGooglePlaces, socialPlatformDetector
│ ├── styles/ # designSystem.ts (tokens, layout patterns)
│
├── docs/ # Architecture and usage docs (⚠ some outdated)
│ ├── backend-reference/ # openapi.json (Adobe Event Service API spec)
│ └── v1-reference/ # Legacy data-utils.js reference
│
└── .github/workflows/ # CI/CD
├── deploy_prod.yml # Deploy on release
├── deploy_stage.yml # Deploy on push to main
└── pr_test.yml # Test on PR (matrix: macOS, ubuntu, windows)
```
---
## Routes (code-validated from App.tsx)
| Path | Component | Purpose |
| -------------------------- | ------------------------ | -------------------------- |
| `/` | Home | Landing page |
| `/overview` | OverviewDashboard | Cross-entity overview |
| `/profile` | UserProfile | User profile |
| `/clouds` | CloudManagementConsole | Cloud/series settings |
| `/series` | SeriesDashboard | Series list |
| `/series/new` | SeriesForm | Create series |
| `/series/edit/:id` | SeriesForm | Edit series |
| `/events` | EventsDashboard | Events list |
| `/events/new/:eventType` | EventForm | Create event (InPerson/Webinar) |
| `/events/edit/:id` | EventForm | Edit event |
| `/attendees` | AttendeeDashboard | Attendee list |
| `/attendees/:eventId` | AttendeeDashboard | Attendees for event |
| `/speakers` | SpeakersDashboard | Speakers list |
| `/about` | About | About page |
---
## Architecture Patterns
### State Management
- **React Context + useReducer** for complex state (EventFormContext, SeriesFormContext, ToastContext)
- **ApiProvider** wraps app with auth headers and API service instance
- No Redux, Zustand, or other state libraries
### API Layer
- **ApiService** class (`services/api.ts`) — centralized HTTP client
- **cachedApi** — wrapper with 10s GET caching, request dedup, pattern-based invalidation
- Two API backends: Adobe ESP (events platform) and ESL (events service layer)
- Auth: Bearer token (IMS or dev token) + `x-gw-ims-org-id`
- **Dry-run mode** for safe testing: `apiService.enableDryRun()`
- Input validation: `validateString()`, `validateObject()` before API calls
- ALLOWED_HOSTS allowlist for fetch URL validation
### Form System
- **FormWizard** — multi-step form with progress, step locking, back/next
- **useEventFormComponent** hook — component registration with lifecycle callbacks:
- `onGatherPayload()` — contribute partial data during save
- `onAfterSave(eventId, response)` — post-save side effects (venue, images, etc.)
- `onLoadResponse(response)` — populate from API response
- `validate()` — pre-save validation
- **Form persistence** via sessionStorage (debounced auto-save of drafts)
### Component Organization
- **Pages** in `pages/` — feature folders with barrel exports
- **Shared** in `components/shared/` — reusable UI building blocks
- **Layout** in `components/layout/` — TopNav only
- Each EventForm step is a modular component in `pages/EventForm/`
---
## NPM Scripts (code-validated)
| Script | Command | Purpose |
| ------------- | ---------------------------------- | ------------------------- |
| `dev` | `PORT=3000 aio app run` | Dev server (port **3000**) |
| `dev:local` | `PORT=3000 aio app run --local` | Fully local dev |
| `lint` | `eslint --ignore-pattern web-src` | Lint backend (src/) |
| `lint:fix` | `npm run lint -- --fix` | Auto-fix lint |
| `type-check` | `tsc --noEmit` | TypeScript check |
**Note:** `npm test` is NOT configured in package.json. Use `aio app test` for Adobe CLI tests.
---
## External Integrations
| Service | Purpose | Config Location |
| ---------------- | ------------------------------------------ | -------------------------- |
| Adobe ESP | Events, series, speakers, sponsors, venues | `config/constants.ts` |
| Adobe ESL | Event lifecycle, venue ops, attendees | `config/constants.ts` |
| Adobe IMS | Authentication (tokens, org, profile) | ExC Shell / tokenStorage |
| Google Places | Venue autocomplete | `utils/loadGooglePlaces` |
| Adobe Chimera | CaaS tags for content | `config/externalConfigs` |
| RSVP configs | Registration form configuration | `services/configService` |
---
## Environments (code-validated from constants.ts + env.ts)
Environment is determined by the **build-time `ENVIRONMENT` variable** (set in CI/CD or `.env`).
`getCurrentEnvironment()` simply returns `env.ENVIRONMENT` — there is no hostname-based detection.
| Environment | ENVIRONMENT value | API Hosts |
| ----------- | ----------------- | ---------------------- |
| local/dev | `dev` (default) | Dev ESL/ESP endpoints |
| stage | `stage` | Stage ESL/ESP |
| prod | `prod` | Prod ESL/ESP |
CI/CD sets this at build time: `ENVIRONMENT: stage` in `deploy_stage.yml`, `ENVIRONMENT: prod` in `deploy_prod.yml`.
---
## Key Conventions
### Code Style
- **Functional components** with hooks — no class components
- **TypeScript** for all new code — types in `types/` directory
- **React Spectrum 2 first** — Follow `.cursor/rules/react-spectrum/react-spectrum-s2.mdc`; use `react-spectrum-v3.mdc` only when S2 has no equivalent
- **UNSAFE_style** / **UNSAFE_className** for non-Spectrum styling (use sparingly)
- **Design tokens** from `styles/designSystem.ts` — prefer over magic numbers (exception: S2 `style()` macro needs literals only — see `react-spectrum-style-macro-s2.mdc` *Static evaluation*)
- **Barrel exports** (`index.ts`) in every module directory
- **No semicolons** in TypeScript files (project convention)
### Naming Conventions
- Components: PascalCase (e.g., `EventInfoComponent.tsx`)
- Hooks: `use` prefix (e.g., `useEventFormComponent.ts`)
- Services: camelCase (e.g., `apiService`, `cachedApi`)
- Types: PascalCase interfaces (e.g., `EventFormData`, `SeriesApiResponse`)
- Constants: UPPER_SNAKE_CASE (e.g., `ALLOWED_HOSTS`, `API_CONFIG`)
- CSS classes: kebab-case (e.g., `.custom-data-table`, `.nav-link`)
### File Organization
- Page components go in `pages/{PageName}/`
- Shared UI goes in `components/shared/`
- Business logic goes in `services/` or `hooks/`
- Type definitions go in `types/`
- Configuration goes in `config/`
---
## Documentation Accuracy Warning
The `docs/` directory contains useful architecture docs but several are **outdated**.
Key discrepancies (validated against code):
| Claim in Docs | Actual Code |
| -------------------------------- | ------------------------------------- |
| Port 9080 | Port **3000** (package.json) |
| EventForm in `components/` | EventForm in `pages/EventForm/` |
| SideBar.tsx | **TopNav.tsx** in `components/layout/` |
| RegistrationDashboard | **AttendeeDashboard** |
| OrgTeamManagement | **CloudManagementConsole** |
| `npm test` / `npm run e2e` | **Not configured** in package.json |
| `test/` and `e2e/` directories | **Do not exist** |
| Route `/events/new` | `/events/new/:eventType` |
| 12 modular form components | **14** components in EventForm |
**Always validate against actual code, not docs.**
---
## Security Considerations
- All API calls go through `safeFetch()` with ALLOWED_HOSTS validation
- Auth tokens handled via IMS (production) or tokenStorage (dev only)
- Dev token features only visible on localhost
- No hardcoded credentials — env vars via `.env` and `app.config.yaml`; per-environment values (e.g. `DEV_CLIENT_IDENTITY`, `STAGE_CLIENT_IDENTITY`, `PROD_CLIENT_IDENTITY`) selected at runtime via `getClientIdentity()` in `config/env.ts`
- `constructRequestHeaders()` adds auth headers consistently
- Input validation before API mutations (`validateString`, `validateObject`)
- See `.cursor/rules/security-global/` for detailed security rules