mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on auth with credentials.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// import {PrismaAdapter} from "@auth/prisma-adapter";
|
||||
// import NextAuth from "next-auth";
|
||||
// import {prisma} from "@/prisma";
|
||||
// import Credentials from "next-auth/providers/credentials";
|
||||
//
|
||||
//
|
||||
// export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
// adapter: PrismaAdapter(prisma),
|
||||
// theme: {
|
||||
// logo: "/icon.png"
|
||||
// },
|
||||
// pages: {
|
||||
// signIn: "/login",
|
||||
// },
|
||||
// debug: process.env.NODE_ENV !== "production",
|
||||
// providers: [
|
||||
// Credentials({
|
||||
// credentials: { password: { label: "Password", type: "password" } },
|
||||
// authorize(c) {
|
||||
// if (c.password !== "password") return null
|
||||
// return {
|
||||
// id: "test",
|
||||
// name: "Test User",
|
||||
// email: "test@example.com",
|
||||
// }
|
||||
// },
|
||||
// }),
|
||||
// ],
|
||||
// callbacks: {
|
||||
// session({ session, user, token }) {
|
||||
// session.user.role = user.role
|
||||
//
|
||||
// return session
|
||||
// }
|
||||
// },
|
||||
// events: {
|
||||
// createUser: async (message) => {
|
||||
// const userId = message.user.id
|
||||
// const userEmail = message.user.email
|
||||
//
|
||||
// if (!userId || !userEmail) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // const stripeCustomer = await stripe.customers.create({
|
||||
// // name: message.user.name ?? "",
|
||||
// // email: userEmail,
|
||||
// // })
|
||||
// //
|
||||
// // await prisma.user.update({
|
||||
// // where: {
|
||||
// // id: userId,
|
||||
// // },
|
||||
// // data: {
|
||||
// // stripeCustomerId: stripeCustomer.id,
|
||||
// // }
|
||||
// // })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// });
|
||||
//
|
||||
// export const providerMap = providers
|
||||
// .map((provider) => {
|
||||
// if (typeof provider === "function") {
|
||||
// const providerData = provider()
|
||||
// return { id: providerData.id, name: providerData.name }
|
||||
// } else {
|
||||
// return { id: provider.id, name: provider.name }
|
||||
// }
|
||||
// })
|
||||
// .filter((provider) => provider.id !== "credentials")
|
||||
|
||||
import NextAuth from "next-auth";
|
||||
import {prisma} from "@/prisma";
|
||||
import {PrismaAdapter} from "@auth/prisma-adapter";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import {env} from "@/env.mjs";
|
||||
|
||||
|
||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
adapter: PrismaAdapter(prisma),
|
||||
theme: {
|
||||
logo: "/icon.png"
|
||||
},
|
||||
secret: env.NEXT_PUBLIC_SECRET,
|
||||
debug: process.env.NODE_ENV !== "production",
|
||||
providers: [
|
||||
Credentials({
|
||||
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
|
||||
// e.g. domain, username, password, 2FA token, etc.
|
||||
credentials: {
|
||||
email: {},
|
||||
password: {},
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
let user = null
|
||||
console.log(credentials);
|
||||
// // logic to salt and hash password
|
||||
// const pwHash = saltAndHashPassword(credentials.password)
|
||||
//
|
||||
// // logic to verify if the user exists
|
||||
// user = await getUserFromDb(credentials.email, pwHash)
|
||||
//
|
||||
// if (!user) {
|
||||
// // No user found, so this is their first attempt to login
|
||||
// // Optionally, this is also the place you could do a user registration
|
||||
// throw new Error("Invalid credentials.")
|
||||
// }
|
||||
//
|
||||
// // return user object with their profile data
|
||||
return user
|
||||
},
|
||||
// credentials: { password: { label: "Password", type: "password" } },
|
||||
// authorize(c) {
|
||||
// if (c.password !== "password") return null
|
||||
// console.log(c)
|
||||
// return {
|
||||
// id: "test",
|
||||
// name: "Test User",
|
||||
// email: "test@example.com",
|
||||
// }
|
||||
// },
|
||||
})
|
||||
],
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
},
|
||||
callbacks: {
|
||||
session({ session, user, token }) {
|
||||
session.user.role = user.role
|
||||
return session
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import {baseAuth} from "@/auth/auth";
|
||||
import {User} from "@prisma/client";
|
||||
|
||||
export const currentUser = async () => {
|
||||
const session = await baseAuth();
|
||||
|
||||
if (!session?.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return session.user as User;
|
||||
}
|
||||
|
||||
export const requiredCurrentUser = async () => {
|
||||
const user = await currentUser();
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {twx} from "@/lib/twx";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
|
||||
export const LayoutAdmin = twx.div((props) => [
|
||||
`w-full h-screen flex flex-col gap-4 mx-auto `
|
||||
]);
|
||||
export const Layout = twx.div((props)=>[
|
||||
`max-w-5xl w-full flex-col flex gap-4 mx-auto px-4 `,
|
||||
])
|
||||
|
||||
export const LayoutTitle = twx.h1((props)=>[
|
||||
cn(`text-4xl font-bold mt-5 `, props.className)
|
||||
])
|
||||
|
||||
export const LayoutDescription = twx.p((props)=>[`text-lg text-muted-foreground`])
|
||||
+167
-115
@@ -1,58 +1,94 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import {
|
||||
Controller,
|
||||
import {cn} from "@/lib/utils";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
import type * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import {Slot} from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import type {
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
SubmitHandler,
|
||||
UseFormProps,
|
||||
UseFormReturn,
|
||||
} from "react-hook-form";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useForm,
|
||||
useFormContext,
|
||||
} from "react-hook-form"
|
||||
} from "react-hook-form";
|
||||
import type {TypeOf, ZodSchema} from "zod";
|
||||
import {Label} from "./label";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
type FormProps<T extends FieldValues> = Omit<
|
||||
React.ComponentProps<"form">,
|
||||
"onSubmit"
|
||||
> & {
|
||||
form: UseFormReturn<T>;
|
||||
onSubmit: SubmitHandler<T>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const Form = FormProvider
|
||||
const Form = <T extends FieldValues>({
|
||||
form,
|
||||
onSubmit,
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
...props
|
||||
}: FormProps<T>) => (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
{...props}
|
||||
className={className}
|
||||
>
|
||||
<fieldset
|
||||
disabled={disabled || form.formState.isSubmitting}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</fieldset>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
{} as FormFieldContextValue
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
<FormFieldContext.Provider value={{name: props.name}}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const {getFieldState, formState} = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
if (!fieldContext.name) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
const {id} = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -61,118 +97,134 @@ const useFormField = () => {
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
{} as FormItemContextValue
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({className, ...props}, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
<FormItemContext.Provider value={{id}}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField()
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({className, ...props}, ref) => {
|
||||
const {error, formItemId} = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({...props}, ref) => {
|
||||
const {error, formItemId, formDescriptionId, formMessageId} =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
error ? `${formDescriptionId} ${formMessageId}` : `${formDescriptionId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField()
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({className, ...props}, ref) => {
|
||||
const {formDescriptionId} = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({className, children, ...props}, ref) => {
|
||||
const {error, formMessageId} = useFormField();
|
||||
const body = error ? String(error.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
type UseZodFormProps<Z extends ZodSchema> = Exclude<
|
||||
UseFormProps<TypeOf<Z>>,
|
||||
"resolver"
|
||||
> & {
|
||||
schema: Z;
|
||||
};
|
||||
|
||||
const useZodForm = <Z extends ZodSchema>({
|
||||
schema,
|
||||
...formProps
|
||||
}: UseZodFormProps<Z>) =>
|
||||
useForm({
|
||||
...formProps,
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useFormField,
|
||||
useZodForm,
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
import {
|
||||
FormControl, FormDescription, 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 {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
import {RegisterSchema, RegisterType} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
|
||||
import {registerUserAction} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.action";
|
||||
|
||||
export type registerFormProps = {
|
||||
defaultValues?: RegisterType;
|
||||
}
|
||||
|
||||
export const RegisterForm = (props: registerFormProps) => {
|
||||
|
||||
const form = useZodForm({
|
||||
schema: RegisterSchema,
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: RegisterType) => {
|
||||
console.log(values)
|
||||
const createUser = await registerUserAction(values);
|
||||
const data = createUser?.data?.data
|
||||
if (createUser?.serverError || !data) {
|
||||
console.log(createUser?.serverError);
|
||||
toast.error(createUser?.serverError);
|
||||
return;
|
||||
}
|
||||
toast.success(`Success`);
|
||||
router.push(`/login`);
|
||||
router.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Register</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form form={form}
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={async (values) => {
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Your name" {...field} />
|
||||
</FormControl>
|
||||
{/*<FormDescription>{t('tabs.general.data.name.description')}</FormDescription>*/}
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Your email" {...field} />
|
||||
</FormControl>
|
||||
{/*<FormDescription>{t('tabs.general.data.name.description')}</FormDescription>*/}
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Your password" {...field} />
|
||||
</FormControl>
|
||||
{/*<FormDescription>{t('tabs.general.data.name.description')}</FormDescription>*/}
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"use server"
|
||||
import {RegisterSchema} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
|
||||
import {action} from "@/safe-actions";
|
||||
import {prisma} from "@/prisma";
|
||||
import {hashPassword} from "@/utils/password";
|
||||
|
||||
|
||||
export const registerUserAction = action
|
||||
.schema(RegisterSchema)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { email: parsedInput.email } });
|
||||
console.log(user);
|
||||
if (!user) {
|
||||
const new_user = await prisma.user.create({
|
||||
data: {
|
||||
email: parsedInput.email,
|
||||
password: await hashPassword(parsedInput.password),
|
||||
},
|
||||
});
|
||||
return {
|
||||
data: new_user,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: user,
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import {z} from "zod";
|
||||
|
||||
|
||||
export const RegisterSchema = z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
export type RegisterType = z.infer<typeof RegisterSchema>;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import {createEnv} from "@t3-oss/env-nextjs";
|
||||
import {z} from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
/*
|
||||
* Serverside Environment variables, not available on the client.
|
||||
* Will throw if you access these variables on the client.
|
||||
*/
|
||||
server: {
|
||||
NODE_ENV: z.enum(['development', 'production']),
|
||||
DATABASE_URL: z.string().url(),
|
||||
SMTP_PASSWORD: z.string(),
|
||||
SMTP_FROM: z.string(),
|
||||
SMTP_HOST: z.string(),
|
||||
SMTP_PORT: z.string(),
|
||||
SMTP_USER: z.string(),
|
||||
NEXT_PUBLIC_SECRET: z.string()
|
||||
},
|
||||
/*
|
||||
* Environment variables available on the client (and server).
|
||||
*
|
||||
* 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_.
|
||||
*/
|
||||
client: {
|
||||
NEXT_PUBLIC_DOMAIN_NAME: z.string(),
|
||||
},
|
||||
/*
|
||||
* Due to how Next.js bundles environment variables on Edge and Client,
|
||||
* we need to manually destructure them to make sure all are included in bundle.
|
||||
*
|
||||
* 💡 You'll get type errors if not all variables from `server` & `client` are included here.
|
||||
*/
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_SECRET: process.env.NEXT_PUBLIC_SECRET,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXT_PUBLIC_DOMAIN_NAME: process.env.NEXT_PUBLIC_DOMAIN_NAME,
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
||||
SMTP_FROM: process.env.SMTP_FROM,
|
||||
SMTP_HOST: process.env.SMTP_HOST,
|
||||
SMTP_PORT: process.env.SMTP_PORT,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createTwc } from "react-twc";
|
||||
import { cn } from "./utils";
|
||||
|
||||
export const twx = createTwc({ compose: cn});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient()
|
||||
}
|
||||
|
||||
declare const globalThis: {
|
||||
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
|
||||
} & typeof global;
|
||||
|
||||
export const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
|
||||
@@ -0,0 +1,31 @@
|
||||
import {createSafeActionClient} from "next-safe-action";
|
||||
import {currentUser} from "@/auth/current-user";
|
||||
|
||||
export class ActionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ActionError";
|
||||
}
|
||||
}
|
||||
|
||||
const handleReturnedServerError = (error: Error) => {
|
||||
if (error instanceof ActionError) {
|
||||
return error.message
|
||||
} else {
|
||||
return "An unexpected error occurred."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const action = createSafeActionClient(
|
||||
{
|
||||
handleReturnedServerError: handleReturnedServerError
|
||||
});
|
||||
|
||||
export const userAction = action.use(async ({ next, ctx }) => {
|
||||
const user = await currentUser();
|
||||
if (!user) {
|
||||
throw new ActionError("You must be logged in");
|
||||
}
|
||||
return next({ ctx: { user } });
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const argon2 = require('argon2');
|
||||
|
||||
const hashedPassword = await argon2.hash(password);
|
||||
return hashedPassword;
|
||||
}
|
||||
Reference in New Issue
Block a user