diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index f7f9a1ec37..e7d5a5b03d 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -20,7 +20,7 @@ export function Footer() {
Hyprnote diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index a3489e80b2..70782f8df7 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -45,7 +45,7 @@ export function Header() { className="font-semibold text-2xl font-serif hover:scale-105 transition-transform mr-4" > Hyprnote @@ -145,7 +145,7 @@ export function Header() { className="sm:hidden font-semibold text-2xl font-serif hover:scale-105 transition-transform" > Hyprnote diff --git a/apps/web/src/components/video-thumbnail.tsx b/apps/web/src/components/video-thumbnail.tsx index 899640e65c..aa2add8b45 100644 --- a/apps/web/src/components/video-thumbnail.tsx +++ b/apps/web/src/components/video-thumbnail.tsx @@ -24,7 +24,7 @@ export function VideoThumbnail({ > ()({ { property: "og:url", content: "https://hyprnote.com" }, { property: "og:image", - content: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/og-image.jpg", + content: "/api/images/hyprnote/og-image.jpg", }, { property: "og:image:width", content: "1200" }, { property: "og:image:height", content: "630" }, @@ -48,8 +47,7 @@ export const Route = createRootRouteWithContext()({ { name: "twitter:url", content: "https://hyprnote.com" }, { name: "twitter:image", - content: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/og-image.jpg", + content: "/api/images/hyprnote/og-image.jpg", }, ], links: [{ rel: "stylesheet", href: appCss }], diff --git a/apps/web/src/routes/_view/about.tsx b/apps/web/src/routes/_view/about.tsx index 387ead3d7d..d284a0318f 100644 --- a/apps/web/src/routes/_view/about.tsx +++ b/apps/web/src/routes/_view/about.tsx @@ -48,8 +48,7 @@ const founders = [ name: "John Jeong", role: "Chief Wisdom Seeker", bio: "I love designing simple and intuitive user interfaces.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/john.png", + image: "/api/images/team/john.png", links: { twitter: "https://x.com/computeless", github: "https://github.com/computelesscomputer", @@ -62,8 +61,7 @@ const founders = [ name: "Yujong Lee", role: "Chief OSS Lover", bio: "I am super bullish about open-source software.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong.png", + image: "/api/images/team/yujong.png", links: { twitter: "https://x.com/yujonglee", github: "https://github.com/yujonglee", @@ -77,72 +75,72 @@ const teamPhotos = [ { id: "john-1", name: "john-1.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/john-1.jpg", + url: "/api/images/team/john-1.jpg", }, { id: "john-2", name: "john-2.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/john-2.jpg", + url: "/api/images/team/john-2.jpg", }, { id: "palo-alto-1", name: "palo-alto-1.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/palo-alto-1.jpg", + url: "/api/images/team/palo-alto-1.jpg", }, { id: "palo-alto-2", name: "palo-alto-2.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/palo-alto-2.jpg", + url: "/api/images/team/palo-alto-2.jpg", }, { id: "palo-alto-3", name: "palo-alto-3.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/palo-alto-3.jpg", + url: "/api/images/team/palo-alto-3.jpg", }, { id: "palo-alto-4", name: "palo-alto-4.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/palo-alto-4.jpg", + url: "/api/images/team/palo-alto-4.jpg", }, { id: "sadang", name: "sadang.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/sadang.jpg", + url: "/api/images/team/sadang.jpg", }, { id: "yc-0", name: "yc-0.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yc-0.jpg", + url: "/api/images/team/yc-0.jpg", }, { id: "yc-1", name: "yc-1.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yc-1.jpg", + url: "/api/images/team/yc-1.jpg", }, { id: "yc-2", name: "yc-2.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yc-2.jpg", + url: "/api/images/team/yc-2.jpg", }, { id: "yujong-1", name: "yujong-1.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong-1.jpg", + url: "/api/images/team/yujong-1.jpg", }, { id: "yujong-2", name: "yujong-2.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong-2.jpg", + url: "/api/images/team/yujong-2.jpg", }, { id: "yujong-3", name: "yujong-3.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong-3.jpg", + url: "/api/images/team/yujong-3.jpg", }, { id: "yujong-4", name: "yujong-4.jpg", - url: "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/team/yujong-4.jpg", + url: "/api/images/team/yujong-4.jpg", }, ]; @@ -279,7 +277,7 @@ function OurStoryGrid({ >
Our Story
Our Story void }) {
Hyprnote Signature 0; const iconUrl = isPrerelease - ? "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/nightly-icon.png" - : "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png"; + ? "/api/images/icons/nightly-icon.png" + : "/api/images/icons/stable-icon.png"; return ( 0; const iconUrl = isPrerelease - ? "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/nightly-icon.png" - : "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png"; + ? "/api/images/icons/nightly-icon.png" + : "/api/images/icons/stable-icon.png"; return (
Hyprnote
No bots interface @@ -687,7 +680,7 @@ export function CoolStuffSection() {
No internet interface @@ -711,7 +704,7 @@ export function CoolStuffSection() {
No bots interface @@ -729,7 +722,7 @@ export function CoolStuffSection() {
No internet interface @@ -1093,7 +1086,7 @@ export function MainFeaturesSection({
Hyprnote ) : ( {`${feature.title} @@ -1368,7 +1361,7 @@ function DetailsMobileCarousel({ /> ) : ( {`${feature.title} @@ -1466,7 +1459,7 @@ function DetailsTabletView({ /> ) : ( {`${detailsFeatures[selectedDetail].title} @@ -1569,7 +1562,7 @@ function DetailsDesktopView() { ) : ( {`${selectedFeature.title} @@ -1586,15 +1579,13 @@ function ManifestoSection() {

@@ -1622,14 +1613,14 @@ function ManifestoSection() {
John Jeong Yujong Lee Hyprnote Signature
Hyprnote
@@ -113,12 +113,12 @@ function Component() { /> {appIcon ? ( Hyprnote diff --git a/apps/web/src/routes/_view/pricing.tsx b/apps/web/src/routes/_view/pricing.tsx index 3f4ea5e9ab..b143fd40fd 100644 --- a/apps/web/src/routes/_view/pricing.tsx +++ b/apps/web/src/routes/_view/pricing.tsx @@ -330,7 +330,7 @@ function CTASection() {
Hyprnote AI assistant AI assistant @@ -419,7 +419,7 @@ function CTASection() {
Hyprnote
AI notetaking in action @@ -626,7 +621,7 @@ function TranscriptionSection() {
On-device transcription @@ -670,7 +665,7 @@ function TranscriptionSection() {
On-device transcription @@ -1033,8 +1028,7 @@ function SearchSection() {
{ const baseCollaborators = [ { name: "Alex Johnson", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/alex-johnson.png", + avatar: "/api/images/mock/alex-johnson.png", scope: "Can view", }, { name: "Jessica Lee", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/jessica-lee.png", + avatar: "/api/images/mock/jessica-lee.png", scope: "Can edit", }, { name: "Sarah Chen", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/sarah-chen.png", + avatar: "/api/images/mock/sarah-chen.png", scope: "Can edit", }, { name: "Michael Park", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/michael-park.png", + avatar: "/api/images/mock/michael-park.png", scope: "Can view", }, { name: "Emily Rodriguez", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/emily-rodriguez.png", + avatar: "/api/images/mock/emily-rodriguez.png", scope: "Can edit", }, ]; const davidKim = { name: "David Kim", - avatar: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/mock/david-kim.png", + avatar: "/api/images/mock/david-kim.png", scope: davidScope, }; @@ -2079,35 +2067,30 @@ const floatingPanelTabs = [ title: "Compact Mode", description: "Minimal overlay that indicates recording is active. Stays out of your way.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/float-compact.jpg", + image: "/api/images/hyprnote/float-compact.jpg", }, { title: "Memos", description: "Take quick notes during the meeting without losing focus on the conversation.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/float-memos.jpg", + image: "/api/images/hyprnote/float-memos.jpg", }, { title: "Transcript", description: "Watch the live transcript as the conversation unfolds in real-time.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/float-transcript.jpg", + image: "/api/images/hyprnote/float-transcript.jpg", }, { title: "Live Insights", description: "Rolling summary of the past 5 minutes with AI suggestions and next steps.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/float-insights.jpg", + image: "/api/images/hyprnote/float-insights.jpg", }, { title: "Chat", description: "Ask questions and get instant answers during the meeting.", - image: - "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/hyprnote/float-chat.jpg", + image: "/api/images/hyprnote/float-chat.jpg", }, ]; @@ -2329,7 +2312,7 @@ function CTASection() {
Hyprnote
Hyprnote
Hyprnote Hyprnote
{integration.name}
Integration
Hyprnote diff --git a/apps/web/src/routes/api/images.$.ts b/apps/web/src/routes/api/images.$.ts new file mode 100644 index 0000000000..334dee2f85 --- /dev/null +++ b/apps/web/src/routes/api/images.$.ts @@ -0,0 +1,75 @@ +import { createFileRoute } from "@tanstack/react-router"; + +const SUPABASE_STORAGE_URL = + "https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images"; + +const SAFE_SEGMENT = /^[A-Za-z0-9._+-]+$/; + +function sanitizePath(raw: string | undefined): string | null { + if (!raw) return null; + + let decoded: string; + try { + decoded = decodeURIComponent(raw); + } catch { + return null; + } + + if (decoded.startsWith("/") || decoded.includes("\\")) { + return null; + } + + const segments = decoded.split("/"); + if (segments.length === 0) return null; + + for (const segment of segments) { + if (!segment) return null; + if (segment === "." || segment === "..") return null; + if (!SAFE_SEGMENT.test(segment)) return null; + } + + return segments.join("/"); +} + +export const Route = createFileRoute("/api/images/$")({ + server: { + handlers: { + GET: async ({ params }) => { + const sanitizedPath = sanitizePath(params._splat); + + if (!sanitizedPath) { + return new Response("Not found", { status: 404 }); + } + + const url = `${SUPABASE_STORAGE_URL}/${sanitizedPath}`; + + const response = await fetch(url); + + if (!response.ok) { + if (response.status === 404) { + return new Response("Not found", { status: 404 }); + } + return new Response("Upstream service error", { status: 502 }); + } + + const contentType = response.headers.get("content-type"); + const cacheControl = response.headers.get("cache-control"); + + const headers: HeadersInit = {}; + if (contentType) { + headers["Content-Type"] = contentType; + } + if (cacheControl) { + headers["Cache-Control"] = cacheControl; + } else { + headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + + return new Response(response.body, { + status: 200, + headers, + }); + }, + }, + }, +}); diff --git a/apps/web/src/routes/auth.tsx b/apps/web/src/routes/auth.tsx index 7aafc028cc..c2d374b966 100644 --- a/apps/web/src/routes/auth.tsx +++ b/apps/web/src/routes/auth.tsx @@ -62,7 +62,7 @@ function Header() { ])} > Hyprnote
{children}