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
34 changes: 22 additions & 12 deletions app/accept/[id]/accept-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,30 @@ import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

export function AcceptClient({ id }: { id: string }) {
export function AcceptClient({ id }: Readonly<{ id: string }>) {
const [busy, setBusy] = useState(false);
const [msg, setMsg] = useState("");
const { data: session, isPending } = authClient.useSession();

let action: React.ReactNode;
if (isPending) {
action = <Loader2 className="size-4 animate-spin" />;
} else if (session) {
action = (
<Button disabled={busy} onClick={async () => {
setBusy(true);
const r = await authClient.organization.acceptInvitation({ invitationId: id });
if (r.error) { setMsg(`✗ ${r.error.message}`); setBusy(false); } else location.href = "/";
}}>{busy && <Loader2 className="size-4 animate-spin" />}Accept invitation</Button>
);
} else {
action = (
<Button disabled={busy} onClick={() => { setBusy(true);
authClient.signIn.social({ provider: "google", callbackURL: `/accept/${id}` }); }}>
{busy && <Loader2 className="size-4 animate-spin" />}Sign in with Google first</Button>
);
}

return (
<main className="page flex justify-center" style={{ paddingTop: 120 }}>
<Card className="w-[400px]">
Expand All @@ -17,17 +37,7 @@ export function AcceptClient({ id }: { id: string }) {
<CardTitle className="text-xl" style={{ fontFamily: "var(--serif)" }}>Join workspace</CardTitle>
</CardHeader>
<CardContent>
{isPending ? <Loader2 className="size-4 animate-spin" /> : !session ? (
<Button disabled={busy} onClick={() => { setBusy(true);
authClient.signIn.social({ provider: "google", callbackURL: `/accept/${id}` }); }}>
{busy && <Loader2 className="size-4 animate-spin" />}Sign in with Google first</Button>
) : (
<Button disabled={busy} onClick={async () => {
setBusy(true);
const r = await authClient.organization.acceptInvitation({ invitationId: id });
if (r.error) { setMsg(`✗ ${r.error.message}`); setBusy(false); } else location.href = "/";
}}>{busy && <Loader2 className="size-4 animate-spin" />}Accept invitation</Button>
)}
{action}
<p className="mt-3 text-sm" style={{ color: "var(--risk)" }}>{msg}</p>
</CardContent>
</Card>
Expand Down
2 changes: 1 addition & 1 deletion app/accept/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AcceptClient } from "./accept-client";
export default async function Accept({ params }: { params: Promise<{ id: string }> }) {
export default async function Accept({ params }: Readonly<{ params: Promise<{ id: string }> }>) {
const { id } = await params;
return <AcceptClient id={id} />;
}
6 changes: 3 additions & 3 deletions app/consent/consent-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";

/** The signed authorization query must go back to the server byte-for-byte. */
const oauthQuery = () => (typeof window === "undefined" ? "" : window.location.search.slice(1));
const oauthQuery = () => (typeof globalThis.window === "undefined" ? "" : globalThis.location.search.slice(1));

export function ConsentForm() {
const [busy, setBusy] = useState<"accept" | "deny" | null>(null);
Expand All @@ -21,7 +21,7 @@ export function ConsentForm() {
});
const data = (await res.json()) as { url?: string; redirect?: boolean };
if (data?.url) {
window.location.href = data.url;
globalThis.location.href = data.url;
return;
}
throw new Error("no redirect url");
Expand Down Expand Up @@ -49,7 +49,7 @@ export function ConsentForm() {
/** No session on the consent page means the flow lost its login; send it back. */
export function ContinueToLogin() {
return (
<Button size="lg" onClick={() => { window.location.href = `/login${window.location.search}`; }}>
<Button size="lg" onClick={() => { globalThis.location.href = `/login${globalThis.location.search}`; }}>
前往登入
</Button>
);
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const metadata = {
title: "Pensieve",
description: "A knowledge graph for HTML documents — sync your repos, fly between your notes.",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>{children}</body>
Expand Down
4 changes: 2 additions & 2 deletions app/login/login-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function LoginPanel() {
// query string. Handing it back verbatim as `oauth_query` is what lets
// better-auth resume /oauth2/authorize once Google returns — reserialising it
// would break the signature.
const oauthQuery = window.location.search.slice(1);
const oauthQuery = globalThis.location.search.slice(1);
try {
const res = await fetch("/api/auth/sign-in/social", {
method: "POST",
Expand All @@ -27,7 +27,7 @@ export function LoginPanel() {
});
const data = (await res.json()) as { url?: string };
if (data?.url) {
window.location.href = data.url;
globalThis.location.href = data.url;
return;
}
throw new Error("no redirect url");
Expand Down
4 changes: 2 additions & 2 deletions app/o/[slug]/d/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function GET(req: Request, ctx: { params: Promise<{ slug: string; p
const assetRows = await db.select().from(schema.asset).where(and(
eq(schema.asset.organizationId, access.org.id), eq(schema.asset.path, rawPath))).limit(1);
if (assetRows.length) {
const bytes = Uint8Array.from(atob(assetRows[0].data), (c) => c.charCodeAt(0));
const bytes = Uint8Array.from(atob(assetRows[0].data), (c) => c.codePointAt(0)!);
return new Response(bytes, { headers: { "content-type": assetRows[0].contentType,
"cache-control": "private, max-age=300" } });
}
Expand All @@ -27,7 +27,7 @@ export async function GET(req: Request, ctx: { params: Promise<{ slug: string; p
const nav = `<script>(${navScript.toString()})(${JSON.stringify(slug)},${JSON.stringify(docPath)},${embed})</script>`;
let html = rows[0].html;
// in-workspace links resolve through the org prefix
html = html.replace(/href="(\/[^"#?]+?)(?:\.(?:html|md))?"/g, (_m, p) => `href="/o/${slug}/d${p}"`);
html = html.replaceAll(/href="(\/[^"#?]+?)(?:\.(?:html|md))?"/g, (_m, p) => `href="/o/${slug}/d${p}"`);
html = html.includes("</body>") ? html.replace("</body>", nav + "</body>") : html + nav;
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
}
Expand Down
Loading
Loading