One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links ๐Ÿ“… calendar.xyehr.cn
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #218 from EvanTechDev/feature/share/idlayout

Improve metadata and title handling for shared event pages

authored by

Evan Huang and committed by
GitHub
52fb4dca 42633c1d

+45 -11
+34 -11
app/(app)/share/[id]/layout.tsx
··· 1 1 import { Metadata } from "next" 2 + import { headers } from "next/headers" 2 3 import { ReactNode } from "react" 3 4 4 5 export async function generateMetadata( 5 6 { params }: { params: { id: string } } 6 7 ): Promise<Metadata> { 8 + const fallbackMetadata: Metadata = { 9 + title: "One Calendar", 10 + icons: { 11 + icon: "/icon.svg", 12 + }, 13 + } 14 + 7 15 try { 8 - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" 16 + const headerStore = await headers() 17 + const forwardedHost = headerStore.get("x-forwarded-host") 18 + const host = forwardedHost || headerStore.get("host") 19 + const protocol = headerStore.get("x-forwarded-proto") || "https" 20 + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || (host ? `${protocol}://${host}` : "http://localhost:3000") 9 21 const res = await fetch(`${baseUrl}/api/share?id=${params.id}`, { 10 22 cache: "no-store", 11 23 }) 12 24 13 - if (!res.ok) throw new Error("Failed to fetch shared event") 25 + if (res.status === 401) { 26 + const result = await res.json().catch(() => null) 27 + if (result?.requiresPassword) { 28 + return { 29 + title: "Protected | One Calendar", 30 + icons: { 31 + icon: "/icon.svg", 32 + }, 33 + } 34 + } 35 + return fallbackMetadata 36 + } 37 + 38 + if (!res.ok) { 39 + return fallbackMetadata 40 + } 14 41 15 42 const result = await res.json() 16 43 17 - if (!result.success || !result.data) throw new Error("Invalid share data") 44 + if (!result.success || !result.data) { 45 + return fallbackMetadata 46 + } 18 47 19 48 const event = typeof result.data === "object" ? result.data : JSON.parse(result.data) 20 49 const eventTitle = typeof event.title === "string" ? event.title : "Untitled" ··· 25 54 icon: "/icon.svg", 26 55 }, 27 56 } 28 - } catch (err) { 29 - console.error("[generateMetadata error]", err) 30 - return { 31 - title: "One Calendar", 32 - icons: { 33 - icon: "/icon.svg", 34 - }, 35 - } 57 + } catch { 58 + return fallbackMetadata 36 59 } 37 60 } 38 61
+11
components/app/profile/shared-event.tsx
··· 159 159 fetchSharedEvent(); 160 160 }, [shareId, handle]); 161 161 162 + useEffect(() => { 163 + if (event?.title) { 164 + document.title = `${event.title} | One Calendar`; 165 + return; 166 + } 167 + 168 + if (requiresPassword) { 169 + document.title = "Protected | One Calendar"; 170 + } 171 + }, [event, requiresPassword]); 172 + 162 173 const tryDecryptWithPassword = async () => { 163 174 if (!shareId) return; 164 175