-
Notifications
You must be signed in to change notification settings - Fork 10
@W-20593322 Developer Guidelines Tool #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
e83e718
e37028c
d17e8bb
8d97e75
ab356ed
a4fcafb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Authentication & Session Management | ||
|
|
||
| ## Architecture | ||
|
|
||
| Split-cookie architecture with server/client contexts: | ||
|
|
||
| - **Server middleware** (`auth.server.ts`): Manages SLAS tokens, writes cookies | ||
| - **Client middleware** (`auth.client.ts`): Reads cookies, maintains cache | ||
| - **React Context** (`AuthProvider`): Provides auth state to components | ||
|
|
||
| ## Cookie Design | ||
|
|
||
| | Cookie Name | Purpose | User Type | Expiry | HttpOnly | | ||
| |-------------|---------|-----------|--------|----------| | ||
| | `cc-nx-g` | Guest refresh token | Guest | 30 days | No | | ||
| | `cc-nx` | Registered refresh token | Registered | 90 days | No | | ||
| | `cc-at` | Access token | Both | 30 min | No | | ||
| | `usid` | User session ID | Both | Matches refresh | No | | ||
| | `customerId` | Customer ID | Registered | Matches refresh | No | | ||
|
|
||
| **Key Points**: | ||
|
|
||
| - Only ONE refresh token exists (guest OR registered, never both) | ||
| - User type derived from which refresh token exists | ||
| - Cookies auto-namespaced with `siteId` | ||
| - Tokens auto-refresh when expired | ||
|
|
||
| ## Usage in Loaders/Actions | ||
|
|
||
| ```typescript | ||
| import { getAuth } from '@/middlewares/auth.server'; | ||
|
|
||
| export function loader({ context }: LoaderFunctionArgs) { | ||
| const auth = getAuth(context); | ||
|
|
||
| // Access auth properties | ||
| const accessToken = auth.access_token; | ||
| const customerId = auth.customer_id; | ||
| const isGuest = auth.userType === 'guest'; | ||
| const isRegistered = auth.userType === 'registered'; | ||
|
|
||
| return { isGuest, customerId }; | ||
| } | ||
| ``` | ||
|
|
||
| ## Usage in Components | ||
|
|
||
| ```typescript | ||
| import { useAuth } from '@/providers/auth'; | ||
|
|
||
| export function MyComponent() { | ||
| const auth = useAuth(); | ||
|
|
||
| if (auth?.userType === 'guest') { | ||
| return <LoginPrompt />; | ||
| } | ||
|
|
||
| return <div>Welcome, customer {auth?.customer_id}</div>; | ||
| } | ||
| ``` | ||
|
|
||
| **Reference:** See README-AUTH.md for complete authentication documentation. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # Component Patterns | ||
|
|
||
| ## Use the `createPage` HOC | ||
|
|
||
| The `createPage` higher-order component standardizes page patterns with built-in Suspense and page key handling: | ||
|
|
||
| ```typescript | ||
| import { use } from 'react'; | ||
| import { createPage } from '@/components/create-page'; | ||
|
|
||
| // Define your view component | ||
| function ProductView({ | ||
| product, | ||
| category | ||
| }: { | ||
| product: Promise<Product>; | ||
| category?: Promise<Category> | ||
| }) { | ||
| const productData = use(product); | ||
| const categoryData = category ? use(category) : null; | ||
|
|
||
| return ( | ||
| <div> | ||
| <h1>{productData.name}</h1> | ||
| {categoryData && <p>Category: {categoryData.name}</p>} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Create page with fallback | ||
| const ProductPage = createPage({ | ||
| component: ProductView, | ||
| fallback: <ProductSkeleton /> | ||
| }); | ||
|
|
||
| export default ProductPage; | ||
| ``` | ||
|
|
||
| **Benefits:** | ||
|
|
||
| - Eliminates repetitive Suspense/Await boilerplate | ||
| - Consistent loading states across pages | ||
| - Built-in page key management for navigation transitions | ||
| - Type-safe with full TypeScript support | ||
|
|
||
| ## shadcn/ui Components | ||
|
|
||
| **RULES**: | ||
|
|
||
| - ✅ Add via: `npx shadcn@latest add <component-name>` | ||
| - ❌ DO NOT modify `src/components/ui/` directly | ||
| - ✅ Create custom components elsewhere | ||
|
|
||
| ## Suspense Boundaries | ||
|
|
||
| Use granular Suspense boundaries for better UX: | ||
|
|
||
| ```typescript | ||
| // ✅ RECOMMENDED - Multiple Suspense boundaries | ||
| export default function ProductPage({ loaderData: { product, reviews } }) { | ||
| return ( | ||
| <div> | ||
| <Suspense fallback={<ProductHeaderSkeleton />}> | ||
| <Await resolve={product}> | ||
| {(data) => <ProductHeader product={data} />} | ||
| </Await> | ||
| </Suspense> | ||
|
|
||
| <Suspense fallback={<ReviewsSkeleton />}> | ||
| <Await resolve={reviews}> | ||
| {(data) => <ProductReviews reviews={data} />} | ||
| </Await> | ||
| </Suspense> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // ⚠️ OK - Single Suspense boundary (less granular) | ||
| export default createPage({ | ||
| component: ProductView, | ||
| fallback: <ProductPageSkeleton /> | ||
| }); | ||
| ``` | ||
|
|
||
| ## File Organization | ||
|
|
||
| ``` | ||
| src/components/product-tile/ | ||
| ├── index.tsx # Component | ||
| ├── index.test.tsx # Tests | ||
| └── stories/ | ||
| ├── index.stories.tsx # Storybook stories | ||
| └── __snapshots__/ # Storybook snapshots (optional) | ||
| └── product-tile-snapshot.tsx.snap | ||
|
|
||
| # Skeleton components are separate components | ||
| src/components/product-skeleton/ | ||
| ├── index.tsx | ||
| ├── index.test.tsx | ||
| └── stories/ | ||
| └── index.stories.tsx | ||
| ``` | ||
|
|
||
| ## Best Practices | ||
|
|
||
| 1. **Extract view components** - Separate data handling from presentation | ||
| 2. **Type safety** - Define proper TypeScript interfaces | ||
| 3. **Consistent fallbacks** - Reusable skeleton components | ||
| 4. **Colocate tests** - Keep tests next to components | ||
| 5. **Story coverage** - Create stories for all reusable components |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # Configuration Management | ||
|
|
||
| ## Overview | ||
|
|
||
| All configuration in `config.server.ts` with environment variable overrides. | ||
|
|
||
| ## Adding Configuration | ||
|
|
||
| 1. **Define type in `src/config/schema.ts`**: | ||
|
|
||
| ```typescript | ||
| export type Config = { | ||
| app: { | ||
| myFeature: { | ||
| enabled: boolean; | ||
| maxItems: number; | ||
| }; | ||
| }; | ||
| }; | ||
| ``` | ||
|
|
||
| 2. **Add defaults in `config.server.ts`**: | ||
|
|
||
| ```typescript | ||
| export default defineConfig({ | ||
| app: { | ||
| myFeature: { | ||
| enabled: false, | ||
| maxItems: 10, | ||
| }, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| 3. **Override via environment variables**: | ||
|
|
||
| ```bash | ||
| PUBLIC__app__myFeature__enabled=true | ||
| PUBLIC__app__myFeature__maxItems=20 | ||
| ``` | ||
|
|
||
| ## Usage Patterns | ||
|
|
||
| **In React Components**: | ||
|
|
||
| ```typescript | ||
| import { useConfig } from '@/config'; | ||
|
|
||
| export function MyComponent() { | ||
| const config = useConfig(); | ||
|
|
||
| if (config.app.myFeature.enabled) { | ||
| const maxItems = config.app.myFeature.maxItems; | ||
| // Your feature code | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **In Loaders/Actions**: | ||
|
|
||
| ```typescript | ||
| import { getConfig } from '@/config'; | ||
|
|
||
| export function loader({ context }: LoaderFunctionArgs) { | ||
| const config = getConfig(context); | ||
|
|
||
| if (config.app.myFeature.enabled) { | ||
| // Your loader code | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Environment Variable Rules | ||
|
|
||
| Use the `PUBLIC__` prefix with double underscores (`__`) to set any config path: | ||
|
|
||
| ```bash | ||
| # Environment variable → Config path | ||
| PUBLIC__app__site__locale=en-US → config.app.site.locale | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is no longer used, being replaced by PUBLIC__app__commerce__sites
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you! My branch is behind a few commits. I will update it. We will need to have final update and review to match up latest implementation when release. |
||
| PUBLIC__app__site__currency=EUR → config.app.site.currency | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| ``` | ||
|
|
||
| Values are automatically parsed (numbers, booleans, JSON arrays/objects). | ||
|
|
||
| Rules: | ||
| 1. **`PUBLIC__` prefix**: Exposed to browser (client-safe values) | ||
| 2. **No prefix**: Server-only (secrets, never exposed) | ||
| 3. **`__` separator**: Navigate nested paths (`PUBLIC__app__site__locale`) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| 4. **Case-insensitive**: All casings work | ||
| 5. **Auto-parsing**: Strings, numbers, booleans, JSON | ||
| 6. **Validation**: Paths must exist in `config.server.ts` | ||
|
|
||
| ## Security | ||
|
|
||
| ```bash | ||
| # ✅ Safe for client (PUBLIC__ prefix) | ||
| PUBLIC__app__commerce__api__clientId=abc123 | ||
|
|
||
| # ✅ Server-only (no prefix) | ||
| COMMERCE_API_SLAS_SECRET=your-secret | ||
| ``` | ||
|
|
||
| Read server-only secrets directly from `process.env` - never add to config. | ||
|
|
||
| **Reference:** See src/config/README.md for complete configuration documentation. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we mention where to override vars?