Skip to content

Commit 32a33d8

Browse files
📝 Add docstrings to codex/update-build-docs.sh-for-openapi-generation
Docstrings generation was requested by @shayancoin. * #551 (comment) The following files were modified: * `frontend/src/app/configurator/quote/complete/page.tsx` * `frontend/src/app/dealer/actions.ts` * `frontend/src/app/kitchen-configurator/[sku]/page.tsx` * `frontend/src/app/server-actions/dealerQuotes.ts` * `frontend/src/app/web-vitals.ts` * `frontend/src/lib/auth.ts` * `frontend/tests/e2e/perf/perf-budget-loader.ts` * `frontend/tests/e2e/utils/wasm-decoder-polyfill.ts`
1 parent fdb38a1 commit 32a33d8

File tree

8 files changed

+221
-8
lines changed

8 files changed

+221
-8
lines changed

frontend/src/app/configurator/quote/complete/page.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { useSearchParams } from 'next/navigation';
88

99
export const dynamic = 'force-dynamic';
1010

11+
/**
12+
* Convert a string into a finite number or return `null` for empty or invalid input.
13+
*
14+
* @param value - The string (or `null`) to parse as a number.
15+
* @returns The parsed numeric value, or `null` if `value` is empty or cannot be parsed to a finite number.
16+
*/
1117
function parseNumber(value: string | null): number | null {
1218
if (!value) {
1319
return null;
@@ -16,10 +22,24 @@ function parseNumber(value: string | null): number | null {
1622
return Number.isFinite(parsed) ? parsed : null;
1723
}
1824

25+
/**
26+
* Formats a number as a locale-aware currency string.
27+
*
28+
* @param value - The numeric amount to format
29+
* @param currency - The ISO 4217 currency code to use (for example, "USD" or "EUR")
30+
* @returns A localized currency string representing `value` in the specified `currency`
31+
*/
1932
function formatCurrency(value: number, currency: string) {
2033
return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value);
2134
}
2235

36+
/**
37+
* Render a confirmation page that displays a generated quote, an optional monetary summary, and an optional PDF download link.
38+
*
39+
* The component derives its content from URL search parameters: `currency` (defaults to `USD`), `quoteId`, `subtotal`, `tax`, `total`, and `pdf` (decoded into a URL). Lines for subtotal, tax, and total are shown only when present; a PDF download button is shown when `pdf` is provided.
40+
*
41+
* @returns The rendered JSX element for the quote completion UI.
42+
*/
2343
function QuoteCompleteContent() {
2444
const searchParams = useSearchParams();
2545

@@ -103,10 +123,15 @@ function QuoteCompleteContent() {
103123
);
104124
}
105125

126+
/**
127+
* Renders the quote completion page UI inside a Suspense boundary.
128+
*
129+
* @returns The React element tree for the quote completion page wrapped with `Suspense`.
130+
*/
106131
export default function QuoteCompletePage() {
107132
return (
108133
<Suspense fallback={null}>
109134
<QuoteCompleteContent />
110135
</Suspense>
111136
);
112-
}
137+
}

frontend/src/app/dealer/actions.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import { cookies } from 'next/headers'
44
import { redirect } from 'next/navigation'
55

6+
/**
7+
* Read and validate Supabase connection values from environment variables.
8+
*
9+
* @returns An object whose `url` is the Supabase URL and `anonKey` is the anonymous public key.
10+
* @throws Error if either the Supabase URL or anon key environment variable is not set.
11+
*/
612
function getSupabaseConfig(): { url: string; anonKey: string } {
713
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? process.env.SUPABASE_URL
814
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? process.env.SUPABASE_ANON_KEY
@@ -14,6 +20,11 @@ function getSupabaseConfig(): { url: string; anonKey: string } {
1420
return { url, anonKey }
1521
}
1622

23+
/**
24+
* Build the Supabase REST v1 base URL from the configured Supabase URL.
25+
*
26+
* @returns The REST base URL — the configured Supabase URL with any trailing slash removed and `/rest/v1` appended
27+
*/
1728
function getRestBase(): string {
1829
const { url } = getSupabaseConfig()
1930
return `${url.replace(/\/$/, '')}/rest/v1`
@@ -103,6 +114,13 @@ function extractAccessTokenFromCookie(): string {
103114
redirect('/configurator')
104115
}
105116

117+
/**
118+
* Validate that the current session's role is "dealer" and return the session access token.
119+
*
120+
* Redirects the request to `/configurator` if the decoded token's role is not `dealer`.
121+
*
122+
* @returns The access token extracted from cookies
123+
*/
106124
function requireDealerToken(): string {
107125
const token = extractAccessTokenFromCookie()
108126
const payload = decodeJwtPayload(token)
@@ -121,6 +139,13 @@ function requireDealerToken(): string {
121139
return token
122140
}
123141

142+
/**
143+
* Constructs request headers required for Supabase REST calls.
144+
*
145+
* @param token - The Supabase access token to include in the `Authorization` header
146+
* @param extra - Additional headers to merge into the result
147+
* @returns A HeadersInit object containing `apikey`, `Authorization` with the bearer token, and any provided extra headers
148+
*/
124149
function buildHeaders(token: string, extra?: Record<string, string>): HeadersInit {
125150
const { anonKey } = getSupabaseConfig()
126151
return {
@@ -154,6 +179,14 @@ async function handleSupabaseError(response: Response): Promise<never> {
154179
throw new Error(`Supabase request failed: ${response.status} ${text}`)
155180
}
156181

182+
/**
183+
* Fetches dealer quote records for the authenticated dealer, ordered by creation time descending.
184+
*
185+
* Redirects the user to `/configurator` if the request is unauthorized (HTTP 401 or 403).
186+
*
187+
* @returns An array of `DealerQuote` objects with optional fields normalized (numeric fields are numbers or `null`).
188+
* @throws Error If the Supabase request fails with a non-401/403 response; the error includes the response status and body.
189+
*/
157190
export async function listDealerQuotes(): Promise<DealerQuote[]> {
158191
const token = requireDealerToken()
159192
const REST_BASE = getRestBase()
@@ -196,6 +229,14 @@ export async function listDealerQuotes(): Promise<DealerQuote[]> {
196229
return payload.map((row) => normalizeQuote(row))
197230
}
198231

232+
/**
233+
* Approves a dealer quote by setting its status to "approved" and recording the approval timestamp.
234+
*
235+
* @param quoteId - The ID of the quote to approve
236+
* @returns The updated DealerQuote record after approval
237+
* @throws Error if the Supabase request fails or the response does not include the updated quote record
238+
* @note Redirects the client to /configurator on 401 or 403 responses
239+
*/
199240
export async function approveDealerQuote(quoteId: string): Promise<DealerQuote> {
200241
const token = requireDealerToken()
201242
const REST_BASE = getRestBase()
@@ -234,6 +275,13 @@ export async function approveDealerQuote(quoteId: string): Promise<DealerQuote>
234275
return normalizeQuote(row)
235276
}
236277

278+
/**
279+
* Retrieve a downloadable URL for a dealer quote PDF.
280+
*
281+
* @param quoteId - The dealer quote's id to look up
282+
* @returns An object with the resolved PDF `url`
283+
* @throws Error if the quote PDF is not available yet
284+
*/
237285
export async function getQuoteDownloadUrl(quoteId: string): Promise<{ url: string }> {
238286
const token = requireDealerToken()
239287
const REST_BASE = getRestBase()
@@ -269,4 +317,4 @@ export async function getQuoteDownloadUrl(quoteId: string): Promise<{ url: strin
269317
const normalized = pdfUrl.startsWith('/') ? pdfUrl : `/${pdfUrl}`
270318
const { url: supabaseUrl } = getSupabaseConfig()
271319
return { url: `${supabaseUrl.replace(/\/$/, '')}${normalized}` }
272-
}
320+
}

frontend/src/app/kitchen-configurator/[sku]/page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ async function load(gltf: GLTF & { scene: THREE.Group }, overrides: MaterialOver
3737
)
3838
}
3939

40+
/**
41+
* Loads a GLTF model for the given SKU, applies material overrides, plays its animations, and renders the scene.
42+
*
43+
* @param sku - Product SKU used to locate the GLTF file at `/models/{sku}.glb`
44+
* @returns The GLTF scene as a React element, or `null` if the scene is not available
45+
*/
4046
function KitchenModel({ sku }: { sku: string }) {
4147
const src = useMemo(() => `/models/${sku}.glb`, [sku])
4248
const gltf = useGLTF(src) as unknown as GLTF & { scene: THREE.Group }
@@ -107,4 +113,4 @@ export default function KitchenConfiguratorPage({ params }: { params: { sku: str
107113
)
108114
}
109115

110-
useGLTF.preload('/models/BaseCabinet600.glb')
116+
useGLTF.preload('/models/BaseCabinet600.glb')

frontend/src/app/server-actions/dealerQuotes.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ class DealerAuthorizationError extends Error {
1111
}
1212
}
1313

14+
/**
15+
* Retrieve Supabase connection settings from environment variables.
16+
*
17+
* @returns An object containing `url` (Supabase URL) and `anonKey` (public anon key)
18+
* @throws Error if `NEXT_PUBLIC_SUPABASE_URL` or `NEXT_PUBLIC_SUPABASE_ANON_KEY` is not set
19+
*/
1420
function getSupabaseConfig() {
1521
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
1622
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
@@ -22,6 +28,11 @@ function getSupabaseConfig() {
2228
return { url, anonKey };
2329
}
2430

31+
/**
32+
* Creates a Supabase server client configured to read and persist authentication state via Next.js server cookies.
33+
*
34+
* @returns A Supabase server client instance configured with cookie adapters for auth persistence
35+
*/
2536
function createDealerServerClient(): SupabaseClient<any, any, any> {
2637
const { url, anonKey } = getSupabaseConfig();
2738
const cookieStore = cookies();
@@ -49,6 +60,13 @@ function createDealerServerClient(): SupabaseClient<any, any, any> {
4960
});
5061
}
5162

63+
/**
64+
* Return a Supabase server client for the currently authenticated user after verifying dealer access.
65+
*
66+
* @returns A Supabase server client configured for the current session when the user is authenticated and authorized as a dealer.
67+
* @throws DealerAuthorizationError - if there is no authenticated user or the user is not a dealer.
68+
* @throws Error - if verifying the Supabase session or validating dealer access fails (contains the underlying error message).
69+
*/
5270
async function requireDealerClient(): Promise<SupabaseClient<any, any, any>> {
5371
const supabase = createDealerServerClient();
5472
const {
@@ -219,4 +237,4 @@ export async function approveDealerQuote(quoteId: string): Promise<DealerQuoteRe
219237
return data;
220238
}
221239

222-
export { DealerAuthorizationError };
240+
export { DealerAuthorizationError };

frontend/src/app/web-vitals.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ type MetricWithAttribution =
5454
| MetricFromHandler<Parameters<typeof onLCP>[0]>
5555
| MetricFromHandler<Parameters<typeof onTTFB>[0]>
5656

57+
/**
58+
* Trims the in-memory queue down to MAX_QUEUE_ITEMS by removing items from one end.
59+
*
60+
* @param drop - Which end to remove items from: `'oldest'` removes from the front of the queue, `'newest'` removes from the back
61+
*/
5762
function enforceQueueLimit(drop: 'oldest' | 'newest' = 'oldest') {
5863
while (queue.length > MAX_QUEUE_ITEMS) {
5964
if (drop === 'oldest') {
@@ -64,6 +69,13 @@ function enforceQueueLimit(drop: 'oldest' | 'newest' = 'oldest') {
6469
}
6570
}
6671

72+
/**
73+
* Compute the byte length of the serialized batch (metrics plus sent timestamp).
74+
*
75+
* @param metrics - Array of payloads to include in the serialized batch
76+
* @param sentAt - ISO timestamp string to include alongside the metrics
77+
* @returns The UTF-8 encoded byte length of `JSON.stringify({ sentAt, metrics })`
78+
*/
6779
function calculateChunkSize(metrics: Payload[], sentAt: string): number {
6880
return textEncoder.encode(JSON.stringify({ sentAt, metrics })).length
6981
}
@@ -155,6 +167,11 @@ async function sendChunk(chunk: Payload[], sentAt: string): Promise<boolean> {
155167
}
156168
}
157169

170+
/**
171+
* Sends all queued web-vitals payloads to the configured reporting endpoint.
172+
*
173+
* Splits the current queue into size-constrained chunks, attempts to transmit each chunk, and on failure re‑queues the failed chunk at the front of the queue (enforcing the queue size limit with a newest-item bias). If any items remain queued after processing, schedules a subsequent flush attempt.
174+
*/
158175
async function flushQueue(): Promise<void> {
159176
if (queue.length === 0) {
160177
return
@@ -234,6 +251,13 @@ function enqueue(payload: Payload) {
234251
scheduleFlush();
235252
}
236253

254+
/**
255+
* Schedule a future flush of the queued web-vital payloads.
256+
*
257+
* If running outside a browser or a flush is already scheduled, this function does nothing.
258+
*
259+
* @param delay - Time in milliseconds to wait before invoking the flush (defaults to FLUSH_INTERVAL_MS)
260+
*/
237261
function scheduleFlush(delay = FLUSH_INTERVAL_MS) {
238262
if (typeof window === 'undefined') {
239263
return
@@ -249,6 +273,14 @@ function scheduleFlush(delay = FLUSH_INTERVAL_MS) {
249273
}, delay)
250274
}
251275

276+
/**
277+
* Captures navigation timing entries once and enqueues them as navigation payloads.
278+
*
279+
* If navigation timing is available and has entries, this function records each entry
280+
* (as a plain object), attaches page/href/timestamps and timeOrigin, enqueues a
281+
* navigation payload for each entry, and marks navigation timing as captured to
282+
* avoid duplicate reporting.
283+
*/
252284
function captureNavigationTiming() {
253285
if (navigationCaptured) {
254286
return;
@@ -330,6 +362,12 @@ function sanitizeAttribution(value: unknown): Record<string, unknown> | undefine
330362
return attribution;
331363
}
332364

365+
/**
366+
* Builds a WebVitalPayload from a web-vital metric, extracting attribution and navigation metadata.
367+
*
368+
* @param metric - The reported metric; may include `attribution` and `startTime` which are used in the payload.
369+
* @returns The WebVitalPayload containing the metric details (`name`, `value`, `delta`, `id`, `rating`, `startTime`, `navigationType`, `attribution`) and contextual fields (`page`, `href`, `navigationType`, `timestamp`, `timeOrigin`). The `attribution` value is converted into a plain, JSON-serializable form if present.
370+
*/
333371
function createMetricPayload(metric: MetricWithAttribution): WebVitalPayload {
334372
const attribution = metric.attribution as ({ navigationType?: string } & Record<string, unknown>) | undefined
335373
const navigationType = attribution?.navigationType
@@ -409,4 +447,4 @@ export function flushWebVitalsQueue() {
409447
}
410448

411449
return flushQueue()
412-
}
450+
}

0 commit comments

Comments
 (0)