mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Ready for 1.1.3-rc.3 release.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
import {env} from "@/env.mjs";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTheme} from "next-themes";
|
||||
|
||||
|
||||
export const AuthLogoSection = () => {
|
||||
|
||||
const {resolvedTheme} = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
|
||||
const imageTheme = resolvedTheme === "dark" ? "/images/logo-white.png" : "/images/logo-black.png";
|
||||
|
||||
return (
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md flex items-center justify-center space-x-2">
|
||||
<img
|
||||
className="p-12 text-black dark:text-white"
|
||||
src={imageTheme}
|
||||
alt="Logo"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground -ml-12 -mb-12">v{env.NEXT_PUBLIC_PROJECT_VERSION}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,150 @@
|
||||
// "use client";
|
||||
//
|
||||
// import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
// import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
// import {Input} from "@/components/ui/input";
|
||||
// import {Form} from "@/components/ui/form";
|
||||
// import {Button} from "@/components/ui/button";
|
||||
// import {toast} from "sonner";
|
||||
// import {useMutation} from "@tanstack/react-query";
|
||||
// import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
// import Link from "next/link";
|
||||
// import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
// import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||
// import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||
// import {signIn} from "@/lib/auth/auth-client";
|
||||
// import {useRouter} from "next/navigation";
|
||||
// import {Icon} from "@iconify/react";
|
||||
// import {useEffect, useState} from "react";
|
||||
//
|
||||
// export type loginFormProps = {
|
||||
// defaultValues?: LoginType;
|
||||
// authGoogleEnabled: boolean;
|
||||
//
|
||||
// };
|
||||
//
|
||||
// export const LoginForm = (props: loginFormProps) => {
|
||||
// const router = useRouter();
|
||||
//
|
||||
// const form = useZodForm({
|
||||
// schema: LoginSchema,
|
||||
// });
|
||||
//
|
||||
// const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// const urlParams = new URLSearchParams(window.location.search);
|
||||
// console.log(urlParams);
|
||||
// setUrlParams(urlParams);
|
||||
// const error = urlParams.get("error");
|
||||
// console.log(urlParams.get("redirect"));
|
||||
// if (error?.includes("pending")) {
|
||||
// toast.error("Your account is not active.");
|
||||
// urlParams.delete("error");
|
||||
// window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
||||
// }
|
||||
// }, []);
|
||||
//
|
||||
//
|
||||
// const mutation = useMutation({
|
||||
// mutationFn: async (values: LoginType) => {
|
||||
// const {error} = await signIn.email(
|
||||
// {
|
||||
// password: values.password,
|
||||
// email: values.email,
|
||||
// callbackURL: urlParams?.get("redirect") ?? "/dashboard/profile",
|
||||
// }, {
|
||||
// onSuccess: () => {
|
||||
// toast.success("Login success");
|
||||
// },
|
||||
// });
|
||||
// if (error) {
|
||||
// toast.error(error.message);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
//
|
||||
// const availableProviders: SocialProviderType[] = [];
|
||||
//
|
||||
// if (props.authGoogleEnabled) {
|
||||
// availableProviders.push(
|
||||
// {
|
||||
// id: "google",
|
||||
// name: "Google",
|
||||
// icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return (
|
||||
// <TooltipProvider>
|
||||
// <Card>
|
||||
// <CardHeader>
|
||||
// <div className="grid gap-2 text-center mb-2">
|
||||
// <h1 className="text-3xl font-bold">Login</h1>
|
||||
// <p className="text-balance text-muted-foreground">Enter your informations below to login</p>
|
||||
// </div>
|
||||
// </CardHeader>
|
||||
// <CardContent>
|
||||
// <Form
|
||||
// form={form}
|
||||
// className="flex flex-col gap-4"
|
||||
// onSubmit={async (values) => {
|
||||
// await mutation.mutateAsync(values);
|
||||
// }}
|
||||
// >
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="email"
|
||||
// defaultValue=""
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Email</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input autoComplete="email webauthn"
|
||||
// placeholder="exemple@portabase.io" {...field} />
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="password"
|
||||
// defaultValue=""
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <div className="flex items-center">
|
||||
// <FormLabel>Password</FormLabel>
|
||||
// {/* <Link href={"/forgot-password"} className="ml-auto inline-block text-sm underline">
|
||||
// Forgot your password?
|
||||
// </Link>*/}
|
||||
// </div>
|
||||
// <FormControl>
|
||||
// <PasswordInput autoComplete="current-password webauthn"
|
||||
// placeholder="Your password" {...field} />
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <Button>Sign in</Button>
|
||||
// <div className="mt-4 text-center text-sm">
|
||||
// Don't have an account?{" "}
|
||||
// <Link href={"/register"} className="underline">
|
||||
// Sign up
|
||||
// </Link>
|
||||
// </div>
|
||||
// </Form>
|
||||
// <SocialAuthButton
|
||||
// callBackURL={urlParams?.get("redirect") ?? "/dashboard/profile"}
|
||||
// providers={availableProviders}/>
|
||||
// </CardContent>
|
||||
// </Card>
|
||||
// </TooltipProvider>
|
||||
// );
|
||||
// };
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
@@ -20,7 +167,6 @@ import {useEffect, useState} from "react";
|
||||
export type loginFormProps = {
|
||||
defaultValues?: LoginType;
|
||||
authGoogleEnabled: boolean;
|
||||
|
||||
};
|
||||
|
||||
export const LoginForm = (props: loginFormProps) => {
|
||||
@@ -30,61 +176,67 @@ export const LoginForm = (props: loginFormProps) => {
|
||||
schema: LoginSchema,
|
||||
});
|
||||
|
||||
const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
||||
const [urlParams] = useState(() =>
|
||||
new URLSearchParams(typeof window !== "undefined" ? window.location.search : "")
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
console.log(urlParams);
|
||||
setUrlParams(urlParams);
|
||||
const error = urlParams.get("error");
|
||||
console.log(urlParams.get("redirect"));
|
||||
if (error?.includes("pending")) {
|
||||
toast.error("Your account is not active.");
|
||||
urlParams.delete("error");
|
||||
window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
||||
}
|
||||
}, []);
|
||||
|
||||
}, [urlParams]);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: LoginType) => {
|
||||
const {error} = await signIn.email(
|
||||
{
|
||||
password: values.password,
|
||||
try {
|
||||
const callbackURL =
|
||||
urlParams.get("redirect")?.startsWith("/")
|
||||
? urlParams.get("redirect")
|
||||
: "/dashboard/profile";
|
||||
|
||||
const {error} = await signIn.email({
|
||||
email: values.email,
|
||||
callbackURL: urlParams?.get("redirect") ?? "/dashboard/profile",
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
toast.success("Login success");
|
||||
// router.push("/dashboard/profile");
|
||||
},
|
||||
password: values.password,
|
||||
callbackURL: callbackURL ?? "/dashboard/profile",
|
||||
});
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
toast.success("Login success");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Unexpected client error during login");
|
||||
}
|
||||
},
|
||||
onError: (err: any) => {
|
||||
toast.error(err.message || "Client error");
|
||||
},
|
||||
});
|
||||
|
||||
const availableProviders: SocialProviderType[] = [];
|
||||
|
||||
if (props.authGoogleEnabled) {
|
||||
availableProviders.push(
|
||||
{
|
||||
id: "google",
|
||||
name: "Google",
|
||||
icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
|
||||
},
|
||||
)
|
||||
availableProviders.push({
|
||||
id: "google",
|
||||
name: "Google",
|
||||
icon: <Icon icon="logos:google-icon" width="25" height="25"/>,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="grid gap-2 text-center mb-2">
|
||||
<h1 className="text-3xl font-bold">Login</h1>
|
||||
<p className="text-balance text-muted-foreground">Enter your informations below to login</p>
|
||||
<p className="text-balance text-muted-foreground">
|
||||
Enter your information below to login
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -103,8 +255,11 @@ export const LoginForm = (props: loginFormProps) => {
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input autoComplete="email webauthn"
|
||||
placeholder="exemple@portabase.io" {...field} />
|
||||
<Input
|
||||
autoComplete="email"
|
||||
placeholder="example@portabase.io"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
@@ -118,29 +273,34 @@ export const LoginForm = (props: loginFormProps) => {
|
||||
<FormItem>
|
||||
<div className="flex items-center">
|
||||
<FormLabel>Password</FormLabel>
|
||||
{/* <Link href={"/forgot-password"} className="ml-auto inline-block text-sm underline">
|
||||
Forgot your password?
|
||||
</Link>*/}
|
||||
{/* Optional forgot password link */}
|
||||
</div>
|
||||
<FormControl>
|
||||
<PasswordInput autoComplete="current-password webauthn"
|
||||
placeholder="Your password" {...field} />
|
||||
<PasswordInput
|
||||
autoComplete="current-password"
|
||||
placeholder="Your password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button>Sign in</Button>
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? "Signing in..." : "Sign in"}
|
||||
</Button>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href={"/register"} className="underline">
|
||||
<Link href="/register" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<SocialAuthButton
|
||||
callBackURL={urlParams?.get("redirect") ?? "/dashboard/profile"}
|
||||
providers={availableProviders}/>
|
||||
callBackURL={urlParams.get("redirect") ?? "/dashboard/profile"}
|
||||
providers={availableProviders}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type BackButtonProps = React.ComponentProps<typeof Button> & {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function BackButton({ children, ...props }: BackButtonProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Button onClick={() => router.back()} aria-label={children?.toString()} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -19,10 +19,6 @@ export const sessionsColumns: ColumnDef<Session>[] = [
|
||||
return timeAgo(row.original.expiresAt);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "ipAddress",
|
||||
header: "IP Address",
|
||||
},
|
||||
{
|
||||
id: "device",
|
||||
header: "Device",
|
||||
|
||||
@@ -10,15 +10,16 @@ import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/side
|
||||
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
||||
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
||||
import {LoggedInButton} from "@/components/wrappers/dashboard/common/logged-in/logged-in-button";
|
||||
import {env} from "@/env.mjs";
|
||||
|
||||
export function AppSidebar() {
|
||||
|
||||
const projectName = env.PROJECT_NAME;
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarLogo/>
|
||||
<SidebarLogo projectName={projectName ?? "Portabase"}/>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<OrganizationCombobox/>
|
||||
@@ -32,10 +33,10 @@ export function AppSidebar() {
|
||||
|
||||
<SidebarMenu className="mb-2">
|
||||
<SidebarMenuItem>
|
||||
<LoggedInButton />
|
||||
<LoggedInButton/>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<SideBarFooterCredit />
|
||||
<SideBarFooterCredit/>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import Link from "next/link";
|
||||
import { env } from "@/env.mjs";
|
||||
import { useTheme } from "next-themes";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const SidebarLogo = () => {
|
||||
export const SidebarLogo = ({projectName}: {
|
||||
projectName: string;
|
||||
}) => {
|
||||
const { state, isMobile } = useSidebar();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
@@ -28,7 +29,7 @@ export const SidebarLogo = () => {
|
||||
<Image
|
||||
loading="eager"
|
||||
src={"/images/logo.png"}
|
||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
||||
alt={`Logo ${projectName}`}
|
||||
className="h-10 w-10 object-contain"
|
||||
height={10}
|
||||
width={10}
|
||||
@@ -38,7 +39,7 @@ export const SidebarLogo = () => {
|
||||
<Image
|
||||
loading="eager"
|
||||
src={imageTheme}
|
||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
||||
alt={`Logo ${projectName}`}
|
||||
className="object-contain"
|
||||
width={190}
|
||||
height={90}
|
||||
|
||||
+12
-11
@@ -6,12 +6,15 @@ const {version} = packageJson;
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
NODE_ENV: z.enum(["development", "production"]).optional(),
|
||||
DATABASE_URL: z.string().url().optional(),
|
||||
NEXT_PUBLIC_PROJECT_NAME: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_URL: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
|
||||
NODE_ENV: z.enum(["development", "production"]).optional(),
|
||||
|
||||
DATABASE_URL: z.string().url().optional(),
|
||||
|
||||
PROJECT_NAME: z.string().optional(),
|
||||
PROJECT_DESCRIPTION: z.string().optional(),
|
||||
PROJECT_URL: z.string().optional(),
|
||||
PROJECT_SECRET: z.string().optional(),
|
||||
|
||||
SMTP_PASSWORD: z.string().optional(),
|
||||
@@ -38,17 +41,15 @@ export const env = createEnv({
|
||||
.default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"),
|
||||
},
|
||||
client: {
|
||||
NEXT_PUBLIC_PROJECT_NAME: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_URL: z.string().optional(),
|
||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_PROJECT_NAME: process.env.NEXT_PUBLIC_PROJECT_NAME,
|
||||
NEXT_PUBLIC_PROJECT_DESCRIPTION: process.env.NEXT_PUBLIC_PROJECT_DESCRIPTION,
|
||||
NEXT_PUBLIC_PROJECT_URL: process.env.NEXT_PUBLIC_PROJECT_URL,
|
||||
NEXT_PUBLIC_PROJECT_VERSION: version || "Unknown Version",
|
||||
|
||||
PROJECT_NAME: process.env.PROJECT_NAME,
|
||||
PROJECT_DESCRIPTION: process.env.PROJECT_DESCRIPTION,
|
||||
PROJECT_URL: process.env.PROJECT_URL,
|
||||
PROJECT_SECRET: process.env.PROJECT_SECRET,
|
||||
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
"use client"
|
||||
import {createAuthClient} from "better-auth/react";
|
||||
|
||||
import {adminClient, inferAdditionalFields, organizationClient} from "better-auth/client/plugins";
|
||||
import {env} from "@/env.mjs";
|
||||
import {ac, user, admin as adminRole, pending, superadmin, orgAdmin, orgMember, orgOwner} from "./permissions";
|
||||
import {auth} from "@/lib/auth/auth";
|
||||
|
||||
const baseURL =
|
||||
typeof window === "undefined"
|
||||
? process.env.PROJECT_URL // server side
|
||||
: "";
|
||||
|
||||
const res = await fetch(`${baseURL}/api/config`);
|
||||
const { PROJECT_URL } = await res.json();
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: env.NEXT_PUBLIC_PROJECT_URL,
|
||||
baseURL: PROJECT_URL,
|
||||
plugins: [
|
||||
organizationClient({
|
||||
ac,
|
||||
|
||||
@@ -191,7 +191,7 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
},*/
|
||||
trustedOrigins: [env.NEXT_PUBLIC_PROJECT_URL!, "http://app"],
|
||||
trustedOrigins: [env.PROJECT_URL!, "http://app"],
|
||||
});
|
||||
|
||||
/*export const signUpUser = async (email: string, password: string, name: string) => {
|
||||
|
||||
Reference in New Issue
Block a user