From 8ed455c20ac32c89ae66244a2d25e00daf80334f Mon Sep 17 00:00:00 2001 From: Daniel Hayes Date: Mon, 2 Feb 2026 17:42:03 -0500 Subject: [PATCH] feat(site): add interactive getting started guide (#280) Co-authored-by: Darius Cepulis --- .claude/skills/docs/SKILL.md | 1 + packages/__tech-preview__/react/package.json | 2 +- pnpm-lock.yaml | 359 +++++++++++++++++ site/.gitignore | 5 + site/CLAUDE.md | 30 ++ site/env.d.ts | 12 + site/package.json | 4 + site/src/actions/auth.ts | 69 ++++ site/src/actions/index.ts | 7 + site/src/actions/mux.ts | 175 ++++++++ site/src/components/Code/ClientCode.tsx | 3 +- site/src/components/ImageRadioGroup.tsx | 88 ++++ site/src/components/Select.tsx | 1 + site/src/components/Tabs.tsx | 8 + site/src/components/docs/Selectors.tsx | 21 +- site/src/components/frames/ContentWidth.astro | 32 ++ site/src/components/frames/Minimal.astro | 9 +- .../installation/HTMLUsageCodeBlock.tsx | 107 +++++ .../components/installation/JSPicker.astro | 13 + .../installation/JSPickerClient.tsx | 53 +++ .../installation/MuxUploaderPanel.tsx | 226 +++++++++++ .../installation/ReactCreateCodeBlock.tsx | 103 +++++ .../installation/ReactUsageCodeBlock.tsx | 40 ++ .../installation/RendererPicker.tsx | 25 ++ .../installation/RendererSelect.tsx | 44 ++ .../components/installation/SkinPicker.tsx | 21 + .../installation/UploaderOverlay.tsx | 104 +++++ .../components/installation/UseCasePicker.tsx | 21 + .../content/docs/concepts/architecture.mdx | 22 +- site/src/content/docs/concepts/skins.mdx | 14 +- .../content/docs/concepts/ui-components.mdx | 18 +- .../src/content/docs/concepts/v10-roadmap.mdx | 10 +- .../content/docs/how-to/customize-skins.mdx | 62 +-- site/src/content/docs/how-to/installation.mdx | 225 ++++++----- site/src/content/docs/how-to/write-guides.mdx | 67 ++-- .../docs/reference/fullscreen-button.mdx | 34 +- .../content/docs/reference/mute-button.mdx | 32 +- .../content/docs/reference/play-button.mdx | 32 +- .../content/docs/reference/time-slider.mdx | 32 +- .../content/docs/reference/volume-slider.mdx | 32 +- site/src/layouts/AuthResult.astro | 31 ++ site/src/middleware/__tests__/index.test.ts | 339 ++++++++++++++++ site/src/middleware/index.ts | 118 ++++++ .../pages/api/auth/__tests__/callback.test.ts | 172 ++++++++ site/src/pages/api/auth/callback.ts | 67 ++++ site/src/pages/auth/error.astro | 34 ++ site/src/pages/auth/success.astro | 35 ++ site/src/pages/index.astro | 13 +- site/src/stores/installation.ts | 26 ++ site/src/types/docs.ts | 9 + site/src/utils/auth.ts | 117 ++++++ .../src/utils/mux/__tests__/auth-flow.test.ts | 376 ++++++++++++++++++ site/src/utils/mux/__tests__/polling.test.ts | 180 +++++++++ site/src/utils/mux/auth-flow.ts | 164 ++++++++ site/src/utils/mux/polling.ts | 92 +++++ site/turbo.json | 28 ++ 56 files changed, 3670 insertions(+), 294 deletions(-) create mode 100644 site/env.d.ts create mode 100644 site/src/actions/auth.ts create mode 100644 site/src/actions/index.ts create mode 100644 site/src/actions/mux.ts create mode 100644 site/src/components/ImageRadioGroup.tsx create mode 100644 site/src/components/frames/ContentWidth.astro create mode 100644 site/src/components/installation/HTMLUsageCodeBlock.tsx create mode 100644 site/src/components/installation/JSPicker.astro create mode 100644 site/src/components/installation/JSPickerClient.tsx create mode 100644 site/src/components/installation/MuxUploaderPanel.tsx create mode 100644 site/src/components/installation/ReactCreateCodeBlock.tsx create mode 100644 site/src/components/installation/ReactUsageCodeBlock.tsx create mode 100644 site/src/components/installation/RendererPicker.tsx create mode 100644 site/src/components/installation/RendererSelect.tsx create mode 100644 site/src/components/installation/SkinPicker.tsx create mode 100644 site/src/components/installation/UploaderOverlay.tsx create mode 100644 site/src/components/installation/UseCasePicker.tsx create mode 100644 site/src/layouts/AuthResult.astro create mode 100644 site/src/middleware/__tests__/index.test.ts create mode 100644 site/src/middleware/index.ts create mode 100644 site/src/pages/api/auth/__tests__/callback.test.ts create mode 100644 site/src/pages/api/auth/callback.ts create mode 100644 site/src/pages/auth/error.astro create mode 100644 site/src/pages/auth/success.astro create mode 100644 site/src/stores/installation.ts create mode 100644 site/src/utils/auth.ts create mode 100644 site/src/utils/mux/__tests__/auth-flow.test.ts create mode 100644 site/src/utils/mux/__tests__/polling.test.ts create mode 100644 site/src/utils/mux/auth-flow.ts create mode 100644 site/src/utils/mux/polling.ts create mode 100644 site/turbo.json diff --git a/.claude/skills/docs/SKILL.md b/.claude/skills/docs/SKILL.md index 82814c50..92a88e8f 100644 --- a/.claude/skills/docs/SKILL.md +++ b/.claude/skills/docs/SKILL.md @@ -104,6 +104,7 @@ const player = createPlayer({ src: 'video.mp4' }); **Rules:** +- **Sentence case for headings** — capitalize only the first word and proper nouns (e.g., "Choose your JS framework", not "Choose Your JS Framework") - Active voice, second person ("you") - Short sentences - No filler ("In order to", "basically", "simply") diff --git a/packages/__tech-preview__/react/package.json b/packages/__tech-preview__/react/package.json index f877c0f5..7d808d2d 100644 --- a/packages/__tech-preview__/react/package.json +++ b/packages/__tech-preview__/react/package.json @@ -47,7 +47,7 @@ "build:watch": "tsdown --watch ./src --ignore-watch ./src/icons/generated-icons --silent", "dev": "pnpm run build:watch", "format:icons": "echo noop. Need to replace based on eslint vs. prettier decisions!", - "generate:icons": "svgr --config-file svgr.config.js --out-dir src/icons/generated-icons ../icons/assets && pnpm format:icons", + "generate:icons": "svgr --config-file svgr.config.js --no-prettier --out-dir src/icons/generated-icons ../icons/assets", "test": "echo \"No tests yet\"", "clean": "rm -rf dist src/icons/generated-icons" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7a0b708..04982f3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -330,6 +330,12 @@ importers: '@base-ui-components/react': specifier: 1.0.0-beta.4 version: 1.0.0-beta.4(@types/react@19.2.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mux/mux-node': + specifier: ^12.8.1 + version: 12.8.1 + '@mux/mux-uploader-react': + specifier: ^1.4.1 + version: 1.4.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nanostores/react': specifier: ^1.0.0 version: 1.0.0(nanostores@1.1.0)(react@18.3.1) @@ -357,6 +363,12 @@ importers: github-slugger: specifier: ^2.0.0 version: 2.0.0 + iron-session: + specifier: ^8.0.4 + version: 8.0.4 + jose: + specifier: ^6.1.3 + version: 6.1.3 just-debounce-it: specifier: ^3.2.0 version: 3.2.0 @@ -1374,6 +1386,28 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@mux/mux-node@12.8.1': + resolution: {integrity: sha512-ey2eKn7iwVrjRVJfB/yF9HPDVpEl+fZV00ydx0kc9/BMs+wjBi0KgU0HMqUjTgEoyMUTlHmJ3U3BfDvreyg5iQ==} + + '@mux/mux-uploader-react@1.4.1': + resolution: {integrity: sha512-D5tXivE8k+DJSbUq+8Wp4G9IsyocJQOZdZPOrpoxor2Alcwo/fJ/+gHkNNPsltx6yv1eQ0O5FWTCKSiIOkxy5w==} + peerDependencies: + '@types/react': ^17.0.0 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0 + '@types/react-dom': '*' + react: ^17.0.2 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0 + react-dom: ^17.0.2 || ^17.0.2-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@mux/mux-uploader@1.4.1': + resolution: {integrity: sha512-7ZOrXuv6iODhAcGkj0OI8FINGOF4EKXm4bB4vnQhqgwjP4BsKZr5qFvg78e8oHR3CEA7fNZobk/x4Qtnr4Fnhg==} + + '@mux/upchunk@3.5.0': + resolution: {integrity: sha512-D+TtvlujlZQjh5I+vFzJ31h5E1uVpEaLdR8M3BNaCFbVLnFMZs8J/L/fYSUyVGnyHT/yDtPHn/IHKdo3G6oSjA==} + '@nanostores/react@1.0.0': resolution: {integrity: sha512-eDduyNy+lbQJMg6XxZ/YssQqF6b4OXMFEZMYKPJCCmBevp1lg0g+4ZRi94qGHirMtsNfAWKNwsjOhC+q1gvC+A==} engines: {node: ^20.0.0 || >=22.0.0} @@ -2522,9 +2556,15 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@20.19.30': resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} @@ -2761,6 +2801,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + ajv-errors@3.0.0: resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==} peerDependencies: @@ -2861,6 +2905,9 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2961,6 +3008,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsite@1.0.0: resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} @@ -3089,6 +3140,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -3158,6 +3213,10 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -3317,6 +3376,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3412,6 +3475,9 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-walk@0.1.2: + resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} + domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} @@ -3450,6 +3516,10 @@ packages: oxc-resolver: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3509,9 +3579,25 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -3590,6 +3676,10 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + event-target-shim@6.0.2: + resolution: {integrity: sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==} + engines: {node: '>=10.13.0'} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -3708,6 +3798,17 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -3742,6 +3843,10 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.2.0: resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} @@ -3749,6 +3854,10 @@ packages: resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} engines: {node: '>=16'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -3785,11 +3894,18 @@ packages: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} + global@4.4.0: + resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} + gonzales-pe@4.3.0: resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} engines: {node: '>=0.6.0'} hasBin: true + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3804,6 +3920,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -3890,6 +4014,9 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3952,6 +4079,9 @@ packages: resolution: {integrity: sha512-7Xnt54Dco7uYkfdAw0r2vCly3z0rSaVhEXMzPvl3FndsTVm5p26j+PO+gyinkYmcsEUvX2Rh7OGK7KzYWRu6BA==} hasBin: true + iron-session@8.0.4: + resolution: {integrity: sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -3992,6 +4122,9 @@ packages: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} + is-function@1.0.2: + resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -4095,6 +4228,12 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jpeg-js@0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} @@ -4415,6 +4554,10 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-definitions@6.0.0: resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} @@ -4599,10 +4742,18 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime-types@3.0.2: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} @@ -4615,6 +4766,9 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + min-document@2.19.2: + resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -4759,6 +4913,10 @@ packages: nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -4855,6 +5013,9 @@ packages: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} + parse-headers@2.0.6: + resolution: {integrity: sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==} + parse-imports@2.2.1: resolution: {integrity: sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==} engines: {node: '>= 18'} @@ -5020,6 +5181,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -5060,6 +5224,9 @@ packages: peerDependencies: react: ^19.2.3 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -5763,6 +5930,9 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6064,6 +6234,10 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6163,6 +6337,9 @@ packages: utf-8-validate: optional: true + xhr@2.6.0: + resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -7171,6 +7348,38 @@ snapshots: '@mixmark-io/domino@2.2.0': {} + '@mux/mux-node@12.8.1': + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + jose: 4.15.9 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@mux/mux-uploader-react@1.4.1(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@mux/mux-uploader': 1.4.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@mux/mux-uploader@1.4.1': + dependencies: + '@mux/upchunk': 3.5.0 + + '@mux/upchunk@3.5.0': + dependencies: + event-target-shim: 6.0.2 + xhr: 2.6.0 + '@nanostores/react@1.0.0(nanostores@1.1.0)(react@18.3.1)': dependencies: nanostores: 1.1.0 @@ -8544,8 +8753,17 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 22.19.3 + form-data: 4.0.5 + '@types/node@17.0.45': {} + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@20.19.30': dependencies: undici-types: 6.21.0 @@ -8882,6 +9100,10 @@ snapshots: agent-base@7.1.4: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + ajv-errors@3.0.0(ajv@8.17.1): dependencies: ajv: 8.17.1 @@ -9080,6 +9302,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + axobject-query@4.1.0: {} b4a@1.7.3: {} @@ -9169,6 +9393,11 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsite@1.0.0: {} callsites@3.1.0: {} @@ -9284,6 +9513,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} commander@10.0.1: {} @@ -9340,6 +9573,8 @@ snapshots: cookie-es@1.2.2: {} + cookie@0.7.2: {} + cookie@1.1.1: {} copy-file@11.1.0: @@ -9483,6 +9718,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + dequal@2.0.3: {} destr@2.0.5: {} @@ -9577,6 +9814,8 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 + dom-walk@0.1.2: {} + domelementtype@2.3.0: {} domhandler@5.0.3: @@ -9608,6 +9847,12 @@ snapshots: dts-resolver@2.1.3: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -9651,8 +9896,23 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -9784,6 +10044,8 @@ snapshots: event-target-shim@5.0.1: {} + event-target-shim@6.0.2: {} + eventemitter3@5.0.1: {} events-universal@1.0.1: @@ -9914,6 +10176,21 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -9938,10 +10215,28 @@ snapshots: get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-port-please@3.2.0: {} get-port@7.1.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@5.2.0: dependencies: pump: 3.0.3 @@ -9985,10 +10280,17 @@ snapshots: dependencies: ini: 4.1.1 + global@4.4.0: + dependencies: + min-document: 2.19.2 + process: 0.11.10 + gonzales-pe@4.3.0: dependencies: minimist: 1.2.8 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} h3@1.15.4: @@ -10011,6 +10313,12 @@ snapshots: has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -10188,6 +10496,10 @@ snapshots: human-signals@5.0.0: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -10277,6 +10589,12 @@ snapshots: - ioredis - uploadthing + iron-session@8.0.4: + dependencies: + cookie: 0.7.2 + iron-webcrypto: 1.2.1 + uncrypto: 0.1.3 + iron-webcrypto@1.2.1: {} is-alphabetical@2.0.1: {} @@ -10308,6 +10626,8 @@ snapshots: dependencies: get-east-asian-width: 1.4.0 + is-function@1.0.2: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -10389,6 +10709,10 @@ snapshots: jiti@2.6.1: {} + jose@4.15.9: {} + + jose@6.1.3: {} + jpeg-js@0.4.4: {} js-image-generator@1.0.4: @@ -10724,6 +11048,8 @@ snapshots: markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} + mdast-util-definitions@6.0.0: dependencies: '@types/mdast': 4.0.4 @@ -11178,8 +11504,14 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime-types@3.0.2: dependencies: mime-db: 1.54.0 @@ -11188,6 +11520,10 @@ snapshots: mimic-function@5.0.1: {} + min-document@2.19.2: + dependencies: + dom-walk: 0.1.2 + min-indent@1.0.1: {} minimatch@5.1.6: @@ -11301,6 +11637,8 @@ snapshots: nwsapi@2.2.23: {} + object-assign@4.1.1: {} + obug@2.1.1: {} ofetch@1.5.1: @@ -11402,6 +11740,8 @@ snapshots: parse-gitignore@2.0.0: {} + parse-headers@2.0.6: {} + parse-imports@2.2.1: dependencies: es-module-lexer: 1.7.0 @@ -11562,6 +11902,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + property-information@7.1.0: {} proxy-from-env@1.1.0: {} @@ -11596,6 +11942,8 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-is@16.13.1: {} + react-is@17.0.2: {} react-refresh@0.17.0: {} @@ -12418,6 +12766,8 @@ snapshots: uncrypto@0.1.3: {} + undici-types@5.26.5: {} + undici-types@6.21.0: {} unicode-properties@1.4.1: @@ -12724,6 +13074,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -12819,6 +13171,13 @@ snapshots: ws@8.18.3: {} + xhr@2.6.0: + dependencies: + global: 4.4.0 + is-function: 1.0.2 + parse-headers: 2.0.6 + xtend: 4.0.2 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/site/.gitignore b/site/.gitignore index acfa5d93..3b51c803 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -28,3 +28,8 @@ pnpm-debug.log* # jetbrains setting folder .idea/ +.vercel +.env*.local + +# Local Netlify folder +.netlify diff --git a/site/CLAUDE.md b/site/CLAUDE.md index 61ff329d..0d4b73b8 100644 --- a/site/CLAUDE.md +++ b/site/CLAUDE.md @@ -104,6 +104,36 @@ import clsx from 'clsx'; ``` +### Avoiding Arbitrary Values + +**Prefer token-based utilities or inline styles over arbitrary values:** + +```astro + +
Content
+ + +
Content
+ + +
Content
+``` + +**For responsive/dark mode with non-token values, use CSS custom properties:** + +```astro + +
Content
+ + +
Content
+``` + +This limits the classnames Tailwind must generate. + ## Project Structure ``` diff --git a/site/env.d.ts b/site/env.d.ts new file mode 100644 index 00000000..3ee6a312 --- /dev/null +++ b/site/env.d.ts @@ -0,0 +1,12 @@ +/// + +declare namespace App { + interface Locals { + user?: { + name: string; + email: string; + }; + + accessToken?: string; + } +} diff --git a/site/package.json b/site/package.json index 79c3e119..66b35e48 100644 --- a/site/package.json +++ b/site/package.json @@ -18,6 +18,8 @@ "@astrojs/rss": "^4.0.12", "@astrojs/sitemap": "^3.6.0", "@base-ui-components/react": "1.0.0-beta.4", + "@mux/mux-node": "^12.8.1", + "@mux/mux-uploader-react": "^1.4.1", "@nanostores/react": "^1.0.0", "@pagefind/default-ui": "^1.4.0", "@sentry/astro": "^10.32.1", @@ -27,6 +29,8 @@ "astro": "^5.14.4", "clsx": "^2.1.1", "github-slugger": "^2.0.0", + "iron-session": "^8.0.4", + "jose": "^6.1.3", "just-debounce-it": "^3.2.0", "just-throttle": "^4.2.0", "lucide-react": "^0.546.0", diff --git a/site/src/actions/auth.ts b/site/src/actions/auth.ts new file mode 100644 index 00000000..b2b0df64 --- /dev/null +++ b/site/src/actions/auth.ts @@ -0,0 +1,69 @@ +import { ActionError, defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; +import { SESSION_COOKIE_NAME } from '@/utils/auth'; + +const { OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, OAUTH_URL, PROD } = import.meta.env; + +export const auth = { + /** + * Initiates the OAuth 2.0 login flow + * + * Generates a CSRF-protected state parameter and returns the authorization URL + * for the client to redirect to. The state is stored in a short-lived cookie + * and verified in the callback endpoint. + * + * @returns {authorizationUrl} - The OAuth authorization URL to redirect to + */ + initiateLogin: defineAction({ + handler: async (_input, ctx) => { + // Validate required OAuth configuration + if (!OAUTH_CLIENT_ID || !OAUTH_REDIRECT_URI || !OAUTH_URL) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: 'OAuth configuration missing', + }); + } + + // Generate cryptographically secure random state for CSRF protection + const state = crypto.randomUUID(); + + // Build OAuth 2.0 authorization URL with required parameters + const params = new URLSearchParams({ + client_id: OAUTH_CLIENT_ID, + redirect_uri: OAUTH_REDIRECT_URI, + response_type: 'code', + scope: 'openid profile email offline_access', + state, + }); + + // Store state in a short-lived cookie for verification in the callback + ctx.cookies.set('state', state, { + httpOnly: true, + secure: PROD, + sameSite: 'lax', + maxAge: 600, // Expires after 10 minutes + path: '/', + }); + + return { authorizationUrl: `${OAUTH_URL}/oauth2/authorize?${params.toString()}` }; + }, + }), + + /** + * Logs out the current user by clearing the session cookie + * + * Accepts form submissions to prevent CSRF attacks on logout endpoints. + * + * @returns {success: true} - Indicates successful logout + */ + logout: defineAction({ + accept: 'form', + input: z.object({}), + handler: async (_input, ctx) => { + // Clear the encrypted session cookie + ctx.cookies.delete(SESSION_COOKIE_NAME, { path: '/' }); + + return { success: true }; + }, + }), +}; diff --git a/site/src/actions/index.ts b/site/src/actions/index.ts new file mode 100644 index 00000000..f43c741a --- /dev/null +++ b/site/src/actions/index.ts @@ -0,0 +1,7 @@ +import { auth } from './auth'; +import { mux } from './mux'; + +export const server = { + auth, + mux, +}; diff --git a/site/src/actions/mux.ts b/site/src/actions/mux.ts new file mode 100644 index 00000000..413fb4ab --- /dev/null +++ b/site/src/actions/mux.ts @@ -0,0 +1,175 @@ +import { ActionError, defineAction } from 'astro:actions'; +import { z } from 'astro:schema'; +import Mux from '@mux/mux-node'; + +/** + * Creates an authenticated Mux API client + * + * Uses the access token from the user's session (provided by middleware) + * to authenticate requests to the Mux API. + * + * @param token - OAuth access token from user session + * @throws {Error} If token is missing or invalid + */ +function getMuxClient(token: string | undefined) { + if (!token) { + throw new ActionError({ code: 'UNAUTHORIZED' }); + } + + return new Mux({ + authorizationToken: token, + baseURL: import.meta.env.MUX_API_URL ?? 'https://api.mux.com', + }); +} + +export const mux = { + /** + * List video assets with pagination + * + * Fetches a paginated list of video assets from Mux. + * Requires an authenticated session with valid access token. + * + * @param limit - Number of assets per page (default: 25) + * @param page - Page number to fetch (default: 1) + * @returns Array of video asset objects + */ + listAssets: defineAction({ + input: z.object({ + limit: z.number().optional().default(25), + page: z.number().optional().default(1), + }), + handler: async (input, ctx) => { + const muxClient = getMuxClient(ctx.locals.accessToken); + + try { + const assets = await muxClient.video.assets.list({ + limit: input.limit, + page: input.page, + }); + + return assets.data; + } catch (error) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: error instanceof Error ? error.message : 'Failed to fetch assets', + }); + } + }, + }), + + /** + * Retrieve a single video asset by ID + * + * Fetches detailed information about a specific video asset. + * Requires an authenticated session with valid access token. + * + * @param id - The unique Mux asset ID + * @returns Video asset object with full details + */ + getAsset: defineAction({ + input: z.object({ + id: z.string(), + }), + handler: async (input, ctx) => { + const muxClient = getMuxClient(ctx.locals.accessToken); + + try { + const asset = await muxClient.video.assets.retrieve(input.id); + + return asset; + } catch (error) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: error instanceof Error ? error.message : 'Failed to fetch asset', + }); + } + }, + }), + + /** + * Create a direct upload URL for client-side uploads. + * Returns the signed upload URL and upload ID for tracking. + */ + createDirectUpload: defineAction({ + input: z.object({ + corsOrigin: z.string().optional(), + }), + handler: async (input, ctx) => { + const muxClient = getMuxClient(ctx.locals.accessToken); + + try { + const upload = await muxClient.video.uploads.create({ + cors_origin: input.corsOrigin || '*', + new_asset_settings: { + playback_policy: ['public'], + video_quality: 'basic', + }, + }); + + return { + uploadUrl: upload.url, + uploadId: upload.id, + }; + } catch (error) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: error instanceof Error ? error.message : 'Failed to create upload', + }); + } + }, + }), + + /** + * Poll upload status to get asset_id once processing begins. + * Status: 'waiting' | 'asset_created' | 'errored' | 'cancelled' | 'timed_out' + */ + getUploadStatus: defineAction({ + input: z.object({ + uploadId: z.string(), + }), + handler: async (input, ctx) => { + const muxClient = getMuxClient(ctx.locals.accessToken); + + try { + const upload = await muxClient.video.uploads.retrieve(input.uploadId); + + return { + status: upload.status, + assetId: upload.asset_id, + }; + } catch (error) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: error instanceof Error ? error.message : 'Failed to get upload status', + }); + } + }, + }), + + /** + * Poll asset status to get playback_id once ready. + * Status: 'preparing' | 'ready' | 'errored' + */ + getAssetStatus: defineAction({ + input: z.object({ + assetId: z.string(), + }), + handler: async (input, ctx) => { + const muxClient = getMuxClient(ctx.locals.accessToken); + + try { + const asset = await muxClient.video.assets.retrieve(input.assetId); + + return { + status: asset.status, + playbackId: asset.playback_ids?.[0]?.id, + }; + } catch (error) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: error instanceof Error ? error.message : 'Failed to get asset status', + }); + } + }, + }), +}; diff --git a/site/src/components/Code/ClientCode.tsx b/site/src/components/Code/ClientCode.tsx index 2c2ff27c..eb749888 100644 --- a/site/src/components/Code/ClientCode.tsx +++ b/site/src/components/Code/ClientCode.tsx @@ -1,3 +1,4 @@ +import bash from 'shiki/langs/bash.mjs'; import css from 'shiki/langs/css.mjs'; import html from 'shiki/langs/html.mjs'; import javascript from 'shiki/langs/javascript.mjs'; @@ -7,7 +8,7 @@ import type { SharedProps } from './Shared'; import Shared from './Shared'; const clientHighlighter = await createHighlighter({ - langs: [html, tsx, css, javascript], + langs: [bash, html, tsx, css, javascript], }); /** diff --git a/site/src/components/ImageRadioGroup.tsx b/site/src/components/ImageRadioGroup.tsx new file mode 100644 index 00000000..9c030782 --- /dev/null +++ b/site/src/components/ImageRadioGroup.tsx @@ -0,0 +1,88 @@ +import { Radio } from '@base-ui-components/react/radio'; +import { RadioGroup } from '@base-ui-components/react/radio-group'; +import clsx from 'clsx'; +import type { ReactNode } from 'react'; +import { twMerge } from 'tailwind-merge'; + +export interface ImageRadioOption { + value: T; + label: string; + image: ReactNode; + disabled?: boolean; +} + +export interface ImageRadioGroupProps { + value: T; + onChange: (value: T) => void; + options: ImageRadioOption[]; + 'aria-label': string; + className?: string; + size?: 'sm' | 'md'; + labelPosition?: 'block' | 'inline'; +} + +export default function ImageRadioGroup({ + value, + onChange, + options, + 'aria-label': ariaLabel, + className, + size = 'md', + labelPosition = 'block', +}: ImageRadioGroupProps) { + const boxSize = size === 'sm' ? 8 : 20; + return ( + onChange(newValue as T)} + aria-label={ariaLabel} + className={twMerge(clsx('grid auto-rows-min', size === 'sm' && 'gap-2', size === 'md' && 'gap-4'), className)} + style={{ + gridTemplateColumns: + labelPosition === 'block' + ? `repeat(auto-fill, minmax(calc(var(--spacing) * ${boxSize}), 1fr))` + : 'repeat(auto-fill, minmax(calc(var(--spacing) * 40), 1fr))', + }} + > + {options.map((option) => { + const isSelected = value === option.value; + const isDisabled = option.disabled; + + return ( + +
+ +
{option.image}
+
+ {option.label} +
+ ); + })} +
+ ); +} diff --git a/site/src/components/Select.tsx b/site/src/components/Select.tsx index 4423f925..9418a4b5 100644 --- a/site/src/components/Select.tsx +++ b/site/src/components/Select.tsx @@ -39,6 +39,7 @@ export function Select({ data-testid={dataTestId} > +   diff --git a/site/src/components/Tabs.tsx b/site/src/components/Tabs.tsx index 4605978c..20c94500 100644 --- a/site/src/components/Tabs.tsx +++ b/site/src/components/Tabs.tsx @@ -7,13 +7,21 @@ * namely, that separate islands can't share context, and that, * depending on rendering context, Astro may render the component tree * bottom-up or top-down + * + * IMPORTANT: Use `client:idle` instead of `client:visible` when hydrating + * these components. TabsPanel elements start with `hidden` attribute, + * which prevents them from triggering Intersection Observer visibility, + * causing `client:visible` to never hydrate non-initial panels. */ import clsx from 'clsx'; + import { Check, Copy } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { twMerge } from 'tailwind-merge'; + import useIsHydrated from '@/utils/useIsHydrated'; + import CopyButton from './CopyButton'; interface TabsRootProps { diff --git a/site/src/components/docs/Selectors.tsx b/site/src/components/docs/Selectors.tsx index 20872e5b..5dabeef0 100644 --- a/site/src/components/docs/Selectors.tsx +++ b/site/src/components/docs/Selectors.tsx @@ -2,9 +2,17 @@ import { useStore } from '@nanostores/react'; import { Select } from '@/components/Select'; import { currentStyle as styleStore } from '@/stores/preferences'; import type { AnySupportedStyle, SupportedFramework } from '@/types/docs'; -import { FRAMEWORK_STYLES, isValidFramework, isValidStyleForFramework, SUPPORTED_FRAMEWORKS } from '@/types/docs'; +import { + FRAMEWORK_LABELS, + FRAMEWORK_STYLES, + isValidFramework, + isValidStyleForFramework, + STYLE_LABELS, + SUPPORTED_FRAMEWORKS, +} from '@/types/docs'; import { setStylePreferenceClient, updateStyleAttribute } from '@/utils/docs/preferences'; import { resolveFrameworkChange } from '@/utils/docs/routing'; +import useIsHydrated from '@/utils/useIsHydrated'; interface SelectorProps { currentFramework: SupportedFramework; @@ -12,10 +20,9 @@ interface SelectorProps { } export function Selectors({ currentFramework, currentSlug }: SelectorProps) { - // Read style from nanostore (StyleInit + PreferenceUpdater guarantee a valid value) - // Guard against React hydrating before style is initialized const currentStyle = useStore(styleStore); - if (!currentStyle) return null; + const isHydrated = useIsHydrated(); + const hydrationSafeCurrentStyle = isHydrated ? currentStyle : null; const handleFrameworkChange = (newFramework: SupportedFramework | null) => { if (newFramework === null) return; @@ -52,12 +59,12 @@ export function Selectors({ currentFramework, currentSlug }: SelectorProps) { const frameworkOptions = SUPPORTED_FRAMEWORKS.map((fw) => ({ value: fw, - label: fw, + label: FRAMEWORK_LABELS[fw], })); const styleOptions = availableStyles.map((st) => ({ value: st, - label: st, + label: STYLE_LABELS[st], })); return ( @@ -77,7 +84,7 @@ export function Selectors({ currentFramework, currentSlug }: SelectorProps) { /> Style