add: oidc,passkey,disable email/password,sign-up

This commit is contained in:
Théo LAGACHE
2026-02-13 16:48:13 +01:00
parent 7ab213e5e4
commit 3bf731055f
29 changed files with 6199 additions and 525 deletions
@@ -1,8 +1,7 @@
import {currentUser} from "@/lib/auth/current-user";
import {getAccounts, getSession, getSessions} from "@/lib/auth/auth";
import {LoggedInButtonClient} from "./logged-in-button";
import {SUPPORTED_PROVIDERS} from "../../../../../../portabase.config";
import { currentUser } from "@/lib/auth/current-user";
import { getAccounts, getSession, getSessions } from "@/lib/auth/auth";
import { LoggedInButtonClient } from "./logged-in-button";
import { SUPPORTED_PROVIDERS } from "@/lib/auth/config";
export const LoggedInButton = async () => {
const user = await currentUser();
@@ -10,7 +9,6 @@ export const LoggedInButton = async () => {
const currentSession = await getSession();
const accounts = await getAccounts();
if (!user) return null;
return (
@@ -1,29 +1,21 @@
"use client";
import {ChevronsUpDown} from "lucide-react";
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar";
import {SidebarMenuButton} from "@/components/ui/sidebar";
import {LoggedInDropdown} from "./logged-in-dropdown";
import {Account, Session, User} from "better-auth";
import {AuthProviderConfig} from "../../../../../../portabase.config";
import { ChevronsUpDown } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { LoggedInDropdown } from "./logged-in-dropdown";
import { Account, Session, User } from "better-auth";
import { AuthProviderConfig } from "@/lib/auth/config";
type LoggedInButtonClientProps = {
user: User,
sessions: Session[],
currentSession: Session,
accounts: Account[],
providers: AuthProviderConfig[]
}
export const LoggedInButtonClient = ({
user,
sessions,
currentSession,
accounts,
providers
}: LoggedInButtonClientProps) => {
user: User;
sessions: Session[];
currentSession: Session;
accounts: Account[];
providers: AuthProviderConfig[];
};
export const LoggedInButtonClient = ({ user, sessions, currentSession, accounts, providers }: LoggedInButtonClientProps) => {
return (
<LoggedInDropdown
// @ts-ignore
@@ -40,20 +32,16 @@ export const LoggedInButtonClient = ({
<div className="flex items-center gap-2">
<Avatar className="size-6">
<AvatarFallback>{user.name[0].toUpperCase()}</AvatarFallback>
{user.image && <AvatarImage src={user.image}/>}
{user.image && <AvatarImage src={user.image} />}
</Avatar>
<div className="flex flex-col items-start">
<span
className="text-sm font-medium first-letter:capitalize max-w-[170px] truncate">{user.name}</span>
<span
className="text-xs text-muted-foreground max-w-[170px] truncate"
title={user.email}
>
{user.email}
</span>
<span className="text-sm font-medium first-letter:capitalize max-w-[170px] truncate">{user.name}</span>
<span className="text-xs text-muted-foreground max-w-[170px] truncate" title={user.email}>
{user.email}
</span>
</div>
</div>
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50"/>
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
</SidebarMenuButton>
</LoggedInDropdown>
);
@@ -1,6 +1,6 @@
"use client";
import {PropsWithChildren, ReactNode, useState} from "react";
import { PropsWithChildren, ReactNode, useState } from "react";
import { useRouter } from "next/navigation";
@@ -10,11 +10,11 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSepara
import { signOut } from "@/lib/auth/auth-client";
import {ProfileModal} from "@/components/wrappers/dashboard/common/profile/profile-modal";
import { ProfileModal } from "@/components/wrappers/dashboard/common/profile/profile-modal";
import {Account, Session, User as UserType} from "@/db/schema/02_user";
import { Account, Session, User as UserType } from "@/db/schema/02_user";
import {AuthProviderConfig} from "../../../../../../portabase.config";
import { AuthProviderConfig } from "@/lib/auth/config";
export type LoggedInDropdownProps = PropsWithChildren<{
user: UserType;
@@ -22,13 +22,10 @@ export type LoggedInDropdownProps = PropsWithChildren<{
currentSession: Session;
accounts: Account[];
children: ReactNode;
providers: AuthProviderConfig[]
providers: AuthProviderConfig[];
}>;
export const LoggedInDropdown = ({ user, sessions, currentSession, accounts, children, providers }: LoggedInDropdownProps) => {
const router = useRouter();
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -44,50 +41,46 @@ export const LoggedInDropdown = ({ user, sessions, currentSession, accounts, chi
onOpenChange={setIsModalOpen}
providers={providers}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[var(--radix-popper-anchor-width)] rounded-xl border-2 border-border bg-popover shadow-none p-1"
align="start"
side="top"
sideOffset={8}
>
<DropdownMenuItem
onClick={() => setIsModalOpen(!isModalOpen)}
className="group gap-2 p-1 cursor-pointer rounded-lg mb-1 transition-colors focus:bg-accent hover:bg-accent/50 border border-transparent"
>
<div className="flex size-9 items-center justify-center rounded-md border border-border bg-muted/50 shadow-sm transition-all group-hover:shadow-md group-hover:bg-background">
<User size={18} className="text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium leading-none">Account Settings</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem
className="group gap-2 p-1 cursor-pointer rounded-lg transition-colors focus:bg-red-50 dark:focus:bg-red-950/20 border border-transparent text-red-600 focus:text-red-600"
onClick={async () => {
await signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
},
},
});
}}
>
<div className="flex size-9 items-center justify-center rounded-md border border-red-100 bg-red-50/50 dark:border-red-900/30 dark:bg-red-950/20 shadow-sm transition-all group-hover:shadow-md">
<LogOut size={18} className="text-red-500" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium leading-none">Logout</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[var(--radix-popper-anchor-width)] rounded-xl border-2 border-border bg-popover shadow-none p-1"
align="start"
side="top"
sideOffset={8}
>
<DropdownMenuItem
onClick={() => setIsModalOpen(!isModalOpen)}
className="group gap-2 p-1 cursor-pointer rounded-lg mb-1 transition-colors focus:bg-accent hover:bg-accent/50 border border-transparent"
>
<div className="flex size-9 items-center justify-center rounded-md border border-border bg-muted/50 shadow-sm transition-all group-hover:shadow-md group-hover:bg-background">
<User size={18} className="text-muted-foreground group-hover:text-foreground transition-colors" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium leading-none">Account Settings</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem
className="group gap-2 p-1 cursor-pointer rounded-lg transition-colors focus:bg-red-50 dark:focus:bg-red-950/20 border border-transparent text-red-600 focus:text-red-600"
onClick={async () => {
await signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
},
},
});
}}
>
<div className="flex size-9 items-center justify-center rounded-md border border-red-100 bg-red-50/50 dark:border-red-900/30 dark:bg-red-950/20 shadow-sm transition-all group-hover:shadow-md">
<LogOut size={18} className="text-red-500" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium leading-none">Logout</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
);
};
@@ -1,16 +1,15 @@
"use client";
import React from "react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import { Account, Session, User } from "@/db/schema/02_user";
import { ProfileSidebar } from "./profile-sidebar";
import {ProfileProviders} from "@/components/wrappers/dashboard/profile/profile-providers";
import {ProfileAccount} from "@/components/wrappers/dashboard/profile/profile-account";
import {ProfileAppearance} from "@/components/wrappers/dashboard/profile/profile-apperance";
import {ProfileSecurity} from "@/components/wrappers/dashboard/profile/profile-security";
import {ProfileGeneral} from "@/components/wrappers/dashboard/profile/profile-general";
import {AuthProviderConfig} from "../../../../../../portabase.config";
import { AuthProviderConfig } from "@/lib/auth/config";
import { User, Session, Account } from "@/db/schema/02_user";
import { ProfileGeneral } from "../../profile/profile-general";
import { ProfileSecurity } from "../../profile/profile-security";
import { ProfileProviders } from "../../profile/profile-providers";
import { ProfileAccount } from "../../profile/profile-account";
import { ProfileAppearance } from "../../profile/profile-apperance";
type ProfileModalProps = {
open: boolean;
@@ -19,14 +18,13 @@ type ProfileModalProps = {
currentSession: Session;
accounts: Account[];
onOpenChange: (open: boolean) => void;
providers: AuthProviderConfig[]
providers: AuthProviderConfig[];
};
export const ProfileModal = ({ user, sessions, currentSession, accounts, open, onOpenChange, providers }: ProfileModalProps) => {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[95vw] h-[90vh] max-w-md lg:max-w-[1000px] lg:h-[800px] pb-6 overflow-hidden flex flex-col outline-none gap-0 rounded-xl bg-background">
<DialogContent className="w-[95vw] h-[90vh] max-w-md lg:max-w-[1000px] lg:h-[800px] pb-6 p-0 overflow-hidden flex flex-col outline-none gap-0 rounded-xl bg-background">
<DialogHeader className="sr-only">
<DialogTitle>Settings</DialogTitle>
<DialogDescription>Manage your account settings</DialogDescription>
@@ -45,6 +43,8 @@ export const ProfileModal = ({ user, sessions, currentSession, accounts, open, o
sessions={sessions}
currentSession={currentSession}
credentialAccount={accounts.find((acc) => acc.providerId === "credential")!}
isPasswordEnabled={providers.some((p) => p.id === "credential")}
isPasskeyEnabled={providers.some((p) => p.id === "passkey")}
/>
</TabsContent>
@@ -1,6 +1,6 @@
"use client";
import React, { use } from "react";
import React from "react";
import { TabsList, TabsTrigger } from "@/components/ui/tabs";
import { UserIcon, Settings, Palette, ShieldHalf, Workflow } from "lucide-react";
import { User } from "@/db/schema/02_user";
@@ -10,7 +10,6 @@ interface ProfileSidebarProps {
}
export function ProfileSidebar({ user }: ProfileSidebarProps) {
return (
<div className="w-full lg:w-[260px] flex-shrink-0 lg:border-r bg-muted/10 p-4 lg:p-6 flex flex-col gap-4 border-b lg:border-b-0">
<div className="flex items-center px-2 mb-2">