Add local Pinscope multi-user auth for shared projects.
Self-host email/password accounts enable the existing collaborator invite flow without Clerk; first admin inherits users/local projects. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useOptionalAuth, useOptionalUser } from "@/hooks/use-optional-auth";
|
||||
import { localAuthEnabled } from "@/lib/auth";
|
||||
|
||||
/** When local auth is on, send unsigned users to /sign-in. */
|
||||
export function AuthGate({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { isSignedIn } = useOptionalAuth();
|
||||
const { isLoaded } = useOptionalUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (!localAuthEnabled || !isLoaded) return;
|
||||
if (!isSignedIn) {
|
||||
const next = encodeURIComponent(pathname || "/");
|
||||
router.replace(`/sign-in?next=${next}`);
|
||||
}
|
||||
}, [isLoaded, isSignedIn, pathname, router]);
|
||||
|
||||
if (!localAuthEnabled) return <>{children}</>;
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center text-sm text-muted-foreground">
|
||||
Loading…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!isSignedIn) return null;
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -1,12 +1,59 @@
|
||||
"use client";
|
||||
|
||||
// Open-core seam: the cloud/gateway build replaces this file with the Clerk
|
||||
// user button and live credit balance. The open-source build has neither.
|
||||
// Open-core seam: cloud/gateway replaces with Clerk user button + credits.
|
||||
// Local auth shows email and sign-out / sign-in links.
|
||||
|
||||
import Link from "next/link";
|
||||
import { useOptionalAuth, useOptionalUser } from "@/hooks/use-optional-auth";
|
||||
import { authEnabled, localAuthEnabled } from "@/lib/auth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function SidebarCredits() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function SidebarUserButton() {
|
||||
return null;
|
||||
const { user, isLoaded } = useOptionalUser();
|
||||
const { signOut, isSignedIn } = useOptionalAuth();
|
||||
|
||||
if (!authEnabled || !localAuthEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isLoaded) {
|
||||
return <div className="px-2 text-xs text-muted-foreground">…</div>;
|
||||
}
|
||||
|
||||
if (!isSignedIn || !user) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1 px-1">
|
||||
<Button asChild size="sm" variant="outline" className="w-full justify-start">
|
||||
<Link href="/sign-in">Sign in</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost" className="w-full justify-start">
|
||||
<Link href="/sign-up">Create account</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 px-2 py-1">
|
||||
<div className="truncate text-xs font-medium">{user.name || user.email}</div>
|
||||
{user.email && user.name ? (
|
||||
<div className="truncate text-[11px] text-muted-foreground">{user.email}</div>
|
||||
) : null}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 justify-start px-0 text-xs text-muted-foreground"
|
||||
onClick={() => {
|
||||
signOut?.();
|
||||
window.location.href = "/sign-in";
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user