init: Initial commit
This commit is contained in:
206
components/core/authorize.tsx
Normal file
206
components/core/authorize.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { IconExternalLink, IconHomeFilled, IconUserCircle, IconUser, IconMail, IconChevronRight, IconCheck } from "@tabler/icons-react";
|
||||
|
||||
import permissions from "@/permissions.json";
|
||||
|
||||
export interface AuthorizeContainerProps {
|
||||
client_id: string;
|
||||
redirect_uri: string;
|
||||
response_type: string;
|
||||
scope: string;
|
||||
applicationName: string;
|
||||
applicationUrl: string;
|
||||
applicationLogo?: string;
|
||||
}
|
||||
|
||||
export interface AuthorizeLoadingProps {
|
||||
applicationName: string;
|
||||
applicationLogo?: string;
|
||||
applicationUrl: string;
|
||||
}
|
||||
|
||||
export interface ScopePermission {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const ScopePermissionComponent = (props: ScopePermission) => {
|
||||
const { name, description } = props;
|
||||
return (
|
||||
<div className="flex flex-col gap-1 w-full border border-green-200 bg-green-50 rounded-lg p-4">
|
||||
<div className="flex flex-row gap-2 items-center w-full">
|
||||
<div className="flex justify-center items-center gap-4 bg-green-200 rounded-full p-2">
|
||||
<IconCheck className="w-5 h-5 text-green-900" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 text-sm text-center">{name}</Label>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 text-xs font-light text-center">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuthorizeLoading = (props: AuthorizeLoadingProps) => {
|
||||
const { applicationName, applicationLogo } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-md">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
{applicationLogo && <Image src={applicationLogo} alt={applicationName} width={100} height={100} />}
|
||||
{!applicationLogo && (
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
)}
|
||||
<Label className="text-2xl font-bold text-center">{applicationName}</Label>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0 pt-6">
|
||||
<div className="flex flex-col gap-6 px-6">
|
||||
<div className="flex justify-center items-center gap-6 pb-4">
|
||||
<div className="flex flex-col gap-2 bg-violet-200 rounded-full p-4">
|
||||
<div className="flex justify-center items-center gap-6 border-2 border-violet-900 rounded-full">
|
||||
<Spinner className="size-8 animate-spin text-violet-900" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6 pb-6">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-center">Secured by SecNex</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuthorizeContainer = (props: AuthorizeContainerProps) => {
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [user, setUser] = React.useState<{ username: string, email: string } | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// const handleLogout = async () => {
|
||||
// const response = await fetch("/api/logout");
|
||||
// const data = await response.json();
|
||||
// if (data.success) {
|
||||
// toast.success(data.message);
|
||||
// router.refresh();
|
||||
// } else {
|
||||
// toast.error(data.message);
|
||||
// }
|
||||
// };
|
||||
|
||||
// Get for each scope the permission name and description
|
||||
const scopePermissions = React.useMemo(() => {
|
||||
return props.scope.split(" ").map((scope) => {
|
||||
return {
|
||||
name: permissions[scope as keyof typeof permissions].name,
|
||||
description: permissions[scope as keyof typeof permissions].description,
|
||||
};
|
||||
});
|
||||
}, [props.scope]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkSession = async () => {
|
||||
const response = await fetch("/api/session");
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setUser(data.sessionInfo);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
checkSession();
|
||||
}, [router]);
|
||||
|
||||
if (isLoading) {
|
||||
return <AuthorizeLoading applicationName={props.applicationName} applicationUrl={props.applicationUrl} applicationLogo={props.applicationLogo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-md">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
<Label className="text-2xl font-bold text-center">SecNex</Label>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center">
|
||||
<div className="flex flex-col gap-2 items-center justify-center py-4">
|
||||
<Avatar className="size-14 bg-violet-400">
|
||||
<AvatarImage src={props.applicationLogo} alt={props.applicationName} />
|
||||
<AvatarFallback className="bg-violet-200 text-violet-900 text-lg">
|
||||
{props.applicationName.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 font-bold text-lg">{props.applicationName}</p>
|
||||
<div className="flex flex-row gap-1 items-center justify-center">
|
||||
<IconExternalLink className="w-3 h-3" />
|
||||
<Link href={props.applicationUrl} target="_blank" className="text-zinc-500 text-xs">{props.applicationUrl}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0 pb-6">
|
||||
<div className="flex flex-col gap-6 px-6 justify-start w-full">
|
||||
<div className="flex flex-row gap-4 items-center bg-violet-50 border border-violet-200 rounded-lg p-4">
|
||||
<div className="flex flex-row gap-2 items-center justify-center">
|
||||
<div className="flex justify-center items-center gap-4 bg-violet-200 rounded-full p-3">
|
||||
<IconUserCircle className="w-6 h-6 text-violet-900" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label className="text-zinc-900 dark:text-zinc-300 text-md">{user?.username}</Label>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 text-xs text-wrap break-all">{user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 items-center w-full px-6">
|
||||
<div className="flex flex-row gap-1 items-center w-full">
|
||||
<ShieldCheck className="w-4 h-4 text-violet-900" />
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 text-xs text-center">This application will be able to:</Label>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
{scopePermissions.map((permission) => (
|
||||
<ScopePermissionComponent key={permission.name} name={permission.name} description={permission.description} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6">
|
||||
<Button variant="outline" className="border-violet-900 bg-violet-900 text-violet-50 hover:bg-violet-800 hover:text-violet-100 w-full">Authorize <IconChevronRight className="w-4 h-4" /></Button>
|
||||
<Button variant="outline" className="border-zinc-300 bg-zinc-50 text-zinc-900 hover:bg-zinc-100 hover:text-zinc-800 w-full">Deny</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center justify-center w-full px-6">
|
||||
<p className="text-zinc-700 dark:text-zinc-300 text-xs text-center font-light">
|
||||
By authorizing this application, you agree to the <Link href="/terms" target="_blank" className="text-violet-800 dark:text-violet-300 text-xs font-medium">Terms of Service</Link> and <Link href="/privacy" target="_blank" className="text-violet-800 dark:text-violet-300 text-xs font-medium">Privacy Policy</Link>.
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-center">Secured by SecNex</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
36
components/core/loading.tsx
Normal file
36
components/core/loading.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { IconHomeFilled } from "@tabler/icons-react";
|
||||
import { Card, CardContent, CardTitle, CardHeader } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
export const LoadingContainer = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-md">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
<Label className="text-2xl font-bold text-center">SecNex</Label>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0 pt-6">
|
||||
<div className="flex flex-col gap-6 px-6">
|
||||
<div className="flex justify-center items-center gap-6 pb-4">
|
||||
<div className="flex flex-col gap-2 bg-violet-200 rounded-full p-4">
|
||||
<div className="flex justify-center items-center gap-6 border-2 border-violet-900 rounded-full">
|
||||
<Spinner className="size-8 animate-spin text-violet-900" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6 pb-6">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-center">Secured by SecNex</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
288
components/core/login-form.tsx
Normal file
288
components/core/login-form.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
import UseAnimations from "react-useanimations";
|
||||
import checkAnimation from "react-useanimations/lib/checkmark";
|
||||
|
||||
import {
|
||||
Field,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
} from "@/components/ui/field";
|
||||
|
||||
import { SocialLoginButton } from "@/components/core/social-login-button";
|
||||
|
||||
import { login } from "@/components/server/login";
|
||||
|
||||
import { IconBrandGithub, IconBrandGoogle, IconHomeFilled, IconUserCircle, IconLogout, IconChevronRight } from "@tabler/icons-react";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
|
||||
const loginSchema = z.object({
|
||||
username: z.string().min(3, "Username must be at least 3 characters long"),
|
||||
password: z.string().min(8, "Password must be at least 8 characters long"),
|
||||
});
|
||||
|
||||
export interface LoginFormProps {
|
||||
applicationName: string;
|
||||
applicationLogo?: string;
|
||||
}
|
||||
|
||||
export const LoginForm = () => {
|
||||
const form = useForm<z.infer<typeof loginSchema>>({
|
||||
resolver: zodResolver(loginSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof loginSchema>) => {
|
||||
const response = await login(data.username, data.password);
|
||||
if (response.success) {
|
||||
toast.success(response.message);
|
||||
} else {
|
||||
toast.error(response.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form id="form-login" onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 px-6">
|
||||
<div className="flex flex-col">
|
||||
<FieldGroup>
|
||||
<Controller
|
||||
name="username"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<Field className="flex flex-col gap-2">
|
||||
<FieldLabel>Username</FieldLabel>
|
||||
<Input
|
||||
{...field}
|
||||
type="text"
|
||||
placeholder="Enter your username"
|
||||
className="text-sm"
|
||||
id="form-login-username"
|
||||
autoComplete="username"
|
||||
/>
|
||||
<FieldError className="text-violet-900 text-xs" errors={form.formState.errors.username ? [form.formState.errors.username] : undefined} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="password"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<Field className="flex flex-col gap-2">
|
||||
<FieldLabel>Password</FieldLabel>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="Enter your password"
|
||||
className="text-sm"
|
||||
id="form-login-password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<FieldError className="text-violet-900 text-xs" errors={form.formState.errors.password ? [form.formState.errors.password] : undefined} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</FieldGroup>
|
||||
</div>
|
||||
<Field>
|
||||
<Button type="submit" className="w-full bg-violet-900 text-primary-foreground hover:bg-violet-800">Login</Button>
|
||||
</Field>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoginContainer = (props: LoginFormProps) => {
|
||||
const { applicationName, applicationLogo } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-sm">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
{applicationLogo && <Image src={applicationLogo} alt={applicationName} width={100} height={100} />}
|
||||
{!applicationLogo && (
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
)}
|
||||
<Label className="text-2xl font-bold text-center">{applicationName}</Label>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-center">
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-zinc-700 dark:text-zinc-300 font-bold text-md">Login to your account</p>
|
||||
<p className="text-zinc-500 text-sm">Welcome back! Please sign in to continue.</p>
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0">
|
||||
<div className="flex flex-col gap-6 pb-4">
|
||||
<div className="flex flex-row gap-2 px-6 justify-between items-center w-full">
|
||||
<SocialLoginButton icon={<IconBrandGithub className="w-4 h-4" />} label="GitHub" onClick={() => {}} />
|
||||
<SocialLoginButton icon={<IconBrandGoogle className="w-4 h-4" />} label="Google" onClick={() => {}} />
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-6">
|
||||
<LoginForm />
|
||||
<div className="flex flex-col gap-4">
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 items-center justify-center">
|
||||
<p className="text-zinc-500 text-sm">Don't have an account? <Link href="/register" className="text-violet-800 dark:text-violet-300 font-bold">Sign up</Link></p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs">Secured by SecNex</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoginLoading = (props: LoginFormProps) => {
|
||||
const { applicationName, applicationLogo } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-md">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
{applicationLogo && <Image src={applicationLogo} alt={applicationName} width={100} height={100} />}
|
||||
{!applicationLogo && (
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
)}
|
||||
<Label className="text-2xl font-bold text-center">{applicationName}</Label>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0 pt-6">
|
||||
<div className="flex flex-col gap-6 px-6">
|
||||
<div className="flex justify-center items-center gap-6 pb-4">
|
||||
<div className="flex flex-col gap-2 bg-violet-200 rounded-full p-4">
|
||||
<div className="flex justify-center items-center gap-6 border-2 border-violet-900 rounded-full">
|
||||
<Spinner className="size-8 animate-spin text-violet-900" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6 pb-6">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-center">Secured by SecNex</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoginSuccessContainer = (props: LoginFormProps) => {
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [sessionInfo, setSessionInfo] = React.useState<{ username: string, email: string, role: string } | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { applicationName, applicationLogo } = props;
|
||||
|
||||
const handleLogout = async () => {
|
||||
const response = await fetch("/api/logout");
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
toast.success(data.message);
|
||||
router.refresh();
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkSession = async () => {
|
||||
const response = await fetch("/api/session");
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setSessionInfo(data.sessionInfo);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
router.refresh();
|
||||
}
|
||||
};
|
||||
checkSession();
|
||||
}, [router]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoginLoading applicationName={applicationName} applicationLogo={applicationLogo} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full max-w-md">
|
||||
<Card className="flex flex-col p-0">
|
||||
<CardHeader className="flex flex-col gap-4 justify-center items-center pt-6">
|
||||
<CardTitle className="flex flex-row items-center justify-center gap-1 text-2xl font-bold text-center">
|
||||
{applicationLogo && <Image src={applicationLogo} alt={applicationName} width={100} height={100} />}
|
||||
{!applicationLogo && (
|
||||
<IconHomeFilled className="w-6 h-6" />
|
||||
)}
|
||||
<Label className="text-2xl font-bold text-center">{applicationName}</Label>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-6 px-0 pt-6">
|
||||
<div className="flex flex-col gap-6 px-6">
|
||||
<div className="flex justify-center items-center gap-6 pb-4">
|
||||
<div className="flex flex-col gap-2 bg-violet-200 rounded-full p-4">
|
||||
<div className="flex justify-center items-center gap-6 border-2 border-violet-900 rounded-full">
|
||||
<UseAnimations animation={checkAnimation} size={32} strokeColor="#44168f" fillColor="#44168f" loop={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center justify-center">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-bold text-lg">Successfully signed in</Label>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-wrap break-all">Welcome back, <span className="font-bold">{sessionInfo?.username}</span>!</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4 items-center bg-violet-50 border border-violet-200 rounded-lg p-4">
|
||||
<div className="flex flex-row gap-2 items-center justify-center">
|
||||
<div className="flex justify-center items-center gap-4 bg-violet-200 rounded-full p-3">
|
||||
<IconUserCircle className="w-6 h-6 text-violet-900" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label className="text-zinc-900 dark:text-zinc-300 text-md">{sessionInfo?.username}</Label>
|
||||
<p className="text-zinc-700 dark:text-zinc-300 text-xs text-wrap break-all">{sessionInfo?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6">
|
||||
<Button variant="outline" className="border-violet-900 text-violet-900 hover:text-violet-800 hover:bg-violet-50 w-full">Go to dashboard <IconChevronRight className="w-4 h-4" /></Button>
|
||||
<Button variant="outline" className="border-violet-900 bg-violet-900 text-violet-50 hover:bg-violet-800 hover:text-violet-100 w-full" onClick={handleLogout}><IconLogout className="w-4 h-4" /> Logout</Button>
|
||||
</div>
|
||||
<Separator className="w-full" />
|
||||
<div className="flex flex-col gap-4 items-center justify-center w-full px-6 pb-6">
|
||||
<Label className="text-zinc-700 dark:text-zinc-300 font-medium text-xs text-center">Secured by SecNex</Label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
22
components/core/social-login-button.tsx
Normal file
22
components/core/social-login-button.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SocialMediaButtonProps {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
}
|
||||
|
||||
export const SocialLoginButton = (props: SocialMediaButtonProps) => {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2 items-center w-full", props.className)}>
|
||||
<Button variant="outline" className={cn("w-full", props.buttonClassName)}>
|
||||
{props.icon}
|
||||
{props.label}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user