Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/app/invite/link/[token]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Metadata } from "next";
import { db } from "@/lib/db/client";
import { workspaceShareLinks, workspaces } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { seoConfig, getPageTitle, getFullImageUrl } from "@/lib/seo-config";

type Props = {
params: Promise<{ token: string }>;
children: React.ReactNode;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { token } = await params;

const [shareLink] = await db
.select({
workspaceId: workspaceShareLinks.workspaceId,
expiresAt: workspaceShareLinks.expiresAt,
workspaceName: workspaces.name,
})
.from(workspaceShareLinks)
.innerJoin(workspaces, eq(workspaces.id, workspaceShareLinks.workspaceId))
.where(eq(workspaceShareLinks.token, token))
.limit(1);

if (!shareLink) {
const notFoundTitle = "Invite Link Not Found";
const notFoundDesc = "This invite link is invalid or has been removed.";
return {
title: getPageTitle(notFoundTitle),
description: notFoundDesc,
openGraph: {
title: getPageTitle(notFoundTitle),
description: notFoundDesc,
url: `${seoConfig.siteUrl}/invite/link/${token}`,
siteName: seoConfig.siteName,
images: [{ url: getFullImageUrl(), width: 1200, height: 630, alt: notFoundTitle }],
type: "website",
},
twitter: { card: "summary_large_image", title: getPageTitle(notFoundTitle), description: notFoundDesc },
};
}

if (new Date(shareLink.expiresAt) < new Date()) {
const expiredTitle = "Invite Link Expired";
const expiredDesc = "This workspace invite link has expired.";
return {
title: getPageTitle(expiredTitle),
description: expiredDesc,
openGraph: {
title: getPageTitle(expiredTitle),
description: expiredDesc,
url: `${seoConfig.siteUrl}/invite/link/${token}`,
siteName: seoConfig.siteName,
images: [{ url: getFullImageUrl(), width: 1200, height: 630, alt: expiredTitle }],
type: "website",
},
twitter: { card: "summary_large_image", title: getPageTitle(expiredTitle), description: expiredDesc },
};
}

const workspaceName = shareLink.workspaceName || "Workspace";
const sharedTitle = `Shared Workspace: ${workspaceName}`;
const description = `Join "${workspaceName}" on ThinkEx. Collaborate on notes and build knowledge together.`;
const fullTitle = getPageTitle(sharedTitle);
const url = `${seoConfig.siteUrl}/invite/link/${token}`;

return {
title: fullTitle,
description,
openGraph: {
title: fullTitle,
description,
url,
siteName: seoConfig.siteName,
images: [{ url: getFullImageUrl(), width: 1200, height: 630, alt: sharedTitle }],
type: "website",
},
twitter: {
card: "summary_large_image",
title: fullTitle,
description,
images: [getFullImageUrl()],
},
};
}

export default function InviteLinkLayout({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
41 changes: 34 additions & 7 deletions src/app/share-copy/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { Metadata } from "next";
import { db, workspaces } from "@/lib/db/client";
import { eq } from "drizzle-orm";
import { loadWorkspaceState } from "@/lib/workspace/state-loader";
import { seoConfig, getPageTitle, getFullImageUrl } from "@/lib/seo-config";

type Props = {
params: Promise<{ id: string }>;
children: React.ReactNode;
};

export async function generateMetadata(
{ params }: Props
): Promise<Metadata> {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;

// Fetch workspace basic info
Expand All @@ -21,21 +20,49 @@ export async function generateMetadata(
.limit(1);

if (!workspace[0]) {
const notFoundTitle = "Workspace Not Found";
const notFoundDesc = "The shared workspace could not be found.";
return {
title: "Workspace Not Found",
description: "The shared workspace could not be found.",
title: getPageTitle(notFoundTitle),
description: notFoundDesc,
openGraph: {
title: getPageTitle(notFoundTitle),
description: notFoundDesc,
url: `${seoConfig.siteUrl}/share-copy/${id}`,
siteName: seoConfig.siteName,
images: [{ url: getFullImageUrl(), width: 1200, height: 630, alt: notFoundTitle }],
type: "website",
},
twitter: { card: "summary_large_image", title: getPageTitle(notFoundTitle), description: notFoundDesc },
};
}

// Fetch full state to get potentially updated title/description
const state = await loadWorkspaceState(id);

const title = state.globalTitle || workspace[0].name || "Untitled Workspace";
const sharedTitle = `Shared Workspace: ${title}`;
const description = workspace[0].description || "View and import this shared ThinkEx workspace.";
const fullTitle = getPageTitle(sharedTitle);
const url = `${seoConfig.siteUrl}/share-copy/${id}`;

return {
title: `Shared Workspace: ${title}`,
description: description,
title: fullTitle,
description,
openGraph: {
title: fullTitle,
description,
url,
siteName: seoConfig.siteName,
images: [{ url: getFullImageUrl(), width: 1200, height: 630, alt: sharedTitle }],
type: "website",
},
twitter: {
card: "summary_large_image",
title: fullTitle,
description,
images: [getFullImageUrl()],
},
};
}

Expand Down