diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/App.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/App.tsx index 2c1e918b..bf6a61a7 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/App.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/App.tsx @@ -1,7 +1,7 @@ -// =========================== +// =================== // ©AngelaMos | 2026 // App.tsx -// =========================== +// =================== import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/api/types/event.ts b/PROJECTS/beginner/canary-token-generator/frontend/src/api/types/event.ts index ce939ebc..dc767b7f 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/api/types/event.ts +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/api/types/event.ts @@ -20,6 +20,10 @@ export const geoViewSchema = z.object({ export type GeoView = z.infer +export const eventExtraSchema = z.record(z.string(), z.unknown()) + +export type EventExtra = z.infer + export const eventResponseSchema = z.object({ id: z.number().int().nonnegative(), triggered_at: z.iso.datetime(), @@ -27,7 +31,7 @@ export const eventResponseSchema = z.object({ user_agent: z.string().nullable(), referer: z.string().nullable(), geo: geoViewSchema, - extra: z.unknown(), + extra: eventExtraSchema, notify_status: notifyStatusSchema, notified_at: z.iso.datetime().nullable(), }) diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/components/Glyph/index.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/components/Glyph/index.tsx index 8be24db6..286d9f75 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/components/Glyph/index.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/components/Glyph/index.tsx @@ -52,8 +52,6 @@ function renderGlyph(type: TokenType): React.ReactElement { return case 'mysql': return - default: - return } } diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/LeaderLabel.module.scss b/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/LeaderLabel.module.scss deleted file mode 100644 index f06edcfd..00000000 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/LeaderLabel.module.scss +++ /dev/null @@ -1,57 +0,0 @@ -// =================== -// ©AngelaMos | 2026 -// LeaderLabel.module.scss -// =================== - -@use '../../styles/tokens' as *; -@use '../../styles/fonts' as *; - -.label { - display: flex; - flex-direction: column; - gap: $space-1; - max-width: 22ch; - font-family: $font-mono; - - &[data-align='right'] { - text-align: right; - align-self: flex-end; - } -} - -.head { - display: flex; - align-items: center; - gap: $space-1-5; -} - -.index { - font-size: $font-size-3xs; - letter-spacing: $tracking-wider; - text-transform: uppercase; - color: $ink-mute; - white-space: nowrap; -} - -.rule { - flex: 1 1 auto; - block-size: $hairline; - background-color: $rule; - align-self: center; -} - -.body { - font-size: $font-size-2xs; - line-height: $line-height-tight; - letter-spacing: $tracking-wide; - text-transform: uppercase; - color: $ink; -} - -.caption { - font-size: $font-size-3xs; - line-height: $line-height-snug; - color: $ink-mute; - text-transform: none; - letter-spacing: $tracking-normal; -} diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/index.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/index.tsx deleted file mode 100644 index bf23ae19..00000000 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/components/LeaderLabel/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// =================== -// ©AngelaMos | 2026 -// index.tsx -// =================== - -import type { PropsWithChildren, ReactNode } from 'react' -import styles from './LeaderLabel.module.scss' - -type LeaderLabelProps = PropsWithChildren<{ - index?: string - caption?: ReactNode - align?: 'left' | 'right' -}> - -export function LeaderLabel({ - children, - index, - caption, - align = 'left', -}: LeaderLabelProps): React.ReactElement { - return ( -
-
- {index ? {index} : null} -
-
{children}
- {caption ?
{caption}
: null} -
- ) -} diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/components/SerialBar/index.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/components/SerialBar/index.tsx index a9e07e13..0c5d23ef 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/components/SerialBar/index.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/components/SerialBar/index.tsx @@ -11,14 +11,15 @@ type SerialBarProps = { prefix?: string } -const BAR_CHARS = ['▮', '▯', '▮', '▮', '▯', '▮', '▯', '▯', '▮'] +const BAR_COUNT = 22 +const FALLBACK_BARS = '▮▯▮▮▯▮▯▯▮▮▯▮▮▯▮▯▮▮▯▮▯▮' function fingerprintToBars(value: string): string { if (value.length === 0) { - return BAR_CHARS.join('') + return FALLBACK_BARS } let acc = '' - for (let i = 0; i < 22; i += 1) { + for (let i = 0; i < BAR_COUNT; i += 1) { const ch = value.charCodeAt(i % value.length) acc += (ch & 1) === 0 ? '▮' : '▯' } diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/components/index.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/components/index.tsx index 46d30721..8f845a14 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/components/index.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/components/index.tsx @@ -6,15 +6,9 @@ export { Button } from './Button' export { CopyField } from './CopyField' export { DataRow } from './DataRow' -export { - FieldWrap, - SelectField, - TextareaField, - TextField, -} from './Field' +export { FieldWrap, SelectField, TextareaField, TextField } from './Field' export { Glyph } from './Glyph' export { Halftone } from './Halftone' -export { LeaderLabel } from './LeaderLabel' export { Pill } from './Pill' export { SerialBar } from './SerialBar' export { SpecimenCard, SpecimenCardSection } from './SpecimenCard' diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/main.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/main.tsx index ac319510..4d4c1fff 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/main.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/main.tsx @@ -1,7 +1,7 @@ -// =========================== +// =================== // ©AngelaMos | 2026 // main.tsx -// =========================== +// =================== import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/artifact.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/artifact.tsx index 10aedb6c..ee35b324 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/artifact.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/artifact.tsx @@ -3,7 +3,7 @@ // artifact.tsx // =================== -import { useMemo } from 'react' +import { useEffect, useState } from 'react' import { toast } from 'sonner' import type { Artifact } from '@/api' import { CopyField, Pill } from '@/components' @@ -63,16 +63,7 @@ function FileArtifact({ }): React.ReactElement { const filename = artifact.filename ?? filenameFallback const contentType = artifact.content_type ?? 'application/octet-stream' - const href = useMemo(() => { - if (!artifact.content_b64) { - return null - } - try { - return base64ToObjectUrl(artifact.content_b64, contentType) - } catch (_err) { - return null - } - }, [artifact.content_b64, contentType]) + const href = useBase64ObjectUrl(artifact.content_b64, contentType) if (!href) { return artifact unreadable @@ -109,13 +100,7 @@ function TextArtifact({ const filename = artifact.filename ?? filenameFallback const content = artifact.content ?? '' const contentType = artifact.content_type ?? 'text/plain' - const href = useMemo(() => { - if (content.length === 0) { - return null - } - const blob = new Blob([content], { type: contentType }) - return URL.createObjectURL(blob) - }, [content, contentType]) + const href = useTextObjectUrl(content, contentType) return (
@@ -152,12 +137,57 @@ function ConnectionStringArtifact({ ) } -function base64ToObjectUrl(b64: string, contentType: string): string { +function useBase64ObjectUrl( + b64: string | undefined, + contentType: string +): string | null { + const [url, setUrl] = useState(null) + useEffect(() => { + if (!b64 || b64.length === 0) { + setUrl(null) + return + } + let created: string | null = null + try { + const bytes = base64ToBytes(b64) + const blob = new Blob([bytes], { type: contentType }) + created = URL.createObjectURL(blob) + setUrl(created) + } catch (_err) { + setUrl(null) + } + return () => { + if (created !== null) { + URL.revokeObjectURL(created) + } + } + }, [b64, contentType]) + return url +} + +function useTextObjectUrl(content: string, contentType: string): string | null { + const [url, setUrl] = useState(null) + useEffect(() => { + if (content.length === 0) { + setUrl(null) + return + } + const blob = new Blob([content], { type: contentType }) + const next = URL.createObjectURL(blob) + setUrl(next) + return () => { + URL.revokeObjectURL(next) + } + }, [content, contentType]) + return url +} + +function base64ToBytes(b64: string): Uint8Array { const binary = atob(b64) const len = binary.length const bytes = new Uint8Array(len) for (let i = 0; i < len; i += 1) { bytes[i] = binary.charCodeAt(i) } - return URL.createObjectURL(new Blob([bytes], { type: contentType })) + return bytes } diff --git a/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/index.tsx b/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/index.tsx index 97aeb682..a541d960 100644 --- a/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/index.tsx +++ b/PROJECTS/beginner/canary-token-generator/frontend/src/pages/landing/index.tsx @@ -14,10 +14,16 @@ import { useCreateToken, useTokenTypes, } from '@/api' -import { Button, Halftone, Strip, StripItem } from '@/components' +import { + Button, + Halftone, + Strip, + StripItem, + TextareaField, + TextField, +} from '@/components' import { getTurnstileSiteKey, Turnstile } from '@/core/turnstile' -import { ArtifactDisplay } from './artifact' -import { PAGE_COPY, TOKEN_BLURB } from './copy' +import { PAGE_COPY } from './copy' import { buildSubmissionState, FILE_KIND_TYPES, @@ -224,22 +230,17 @@ function AnnotateSection({ title={PAGE_COPY.SECTION_02_TITLE} body={PAGE_COPY.SECTION_02_BODY} > -
- MEMO -