mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on settings. Adding .env value take in count when they are present in order to prefill the Settings on startup. Adding the SendEMail Feature and the form in settings.
This commit is contained in:
@@ -12,13 +12,11 @@ export default async function Layout({children}: { children: React.ReactNode })
|
||||
email: user?.email
|
||||
}
|
||||
})
|
||||
|
||||
if(userInfo){
|
||||
if(userInfo && userInfo.authMethod){
|
||||
redirect('/dashboard')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<LayoutAdmin>
|
||||
<div
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {PageParams} from "@/types/next";
|
||||
import {redirect} from "next/navigation";
|
||||
|
||||
export default async function RoutePage(props: PageParams<{}>) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
</div>
|
||||
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"/>
|
||||
</div>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-1 flex-col gap-4 p-4">
|
||||
// <div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
// <div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
// <div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
// <div className="aspect-video rounded-xl bg-muted/50"/>
|
||||
// </div>
|
||||
// <div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"/>
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import {PageParams} from "@/types/next";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {redirect} from "next/navigation";
|
||||
|
||||
export default async function RoutePage(props: PageParams<{}>) {
|
||||
redirect("/dashboard/agents")
|
||||
|
||||
return(
|
||||
<div>
|
||||
<h1>Ok</h1>
|
||||
<Button>Button</Button>
|
||||
</div>
|
||||
)
|
||||
// return(
|
||||
// <div>
|
||||
// <h1>Ok</h1>
|
||||
// <Button>Button</Button>
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
@@ -2,15 +2,12 @@ import {PageParams} from "@/types/next";
|
||||
import {Page, PageContent, PageDescription, PageHeader, PageTitle} from "@/features/layout/page";
|
||||
import {prisma} from "@/prisma";
|
||||
import {requiredCurrentUser} from "@/auth/current-user";
|
||||
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
||||
import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users";
|
||||
import {SettingsTabs} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/SettingsTabs";
|
||||
|
||||
|
||||
export default async function RoutePage(props: PageParams<{}>) {
|
||||
const user = await requiredCurrentUser()
|
||||
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
where:{
|
||||
id: {
|
||||
|
||||
@@ -18,6 +18,7 @@ services:
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
container_name: portabase-app
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
Button as ReactEmailButton
|
||||
} from "@react-email/components";
|
||||
import {ComponentPropsWithoutRef} from "react";
|
||||
|
||||
export const Button = (props: ComponentPropsWithoutRef<typeof ReactEmailButton>) => {
|
||||
return (
|
||||
<ReactEmailButton
|
||||
className="block w-52 rounded bg-blue-600 py-3.5 text-center text-sm font-normal text-white no-underline "
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import { PropsWithChildren } from "react";
|
||||
import {getServerUrl} from "@/utils/get-server-url";
|
||||
|
||||
|
||||
|
||||
const baseUrl = getServerUrl();
|
||||
|
||||
export const EmailLayout = ({ children, preview }: PropsWithChildren<{ preview?: string }>) => {
|
||||
return (
|
||||
<Tailwind>
|
||||
<Html>
|
||||
<Head />
|
||||
{preview ? <Preview>{preview}</Preview> : <Preview>Please check your mails</Preview>}
|
||||
<Body className="bg-gray-100 py-4"
|
||||
style={{fontFamily: "Arial, sans-serif"}}>
|
||||
|
||||
<Container className="bg-white border border-gray-200 p-12">
|
||||
<Img
|
||||
src={`${baseUrl}/logo-title-black.png`}
|
||||
width="400"
|
||||
height="auto"
|
||||
alt="Logo"
|
||||
/>
|
||||
<Section>
|
||||
{children}
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
</Tailwind>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default EmailLayout;
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Text,
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import EmailLayout from "./EmailLayout";
|
||||
|
||||
|
||||
export const TestEmailSettings = () => {
|
||||
return (
|
||||
<EmailLayout
|
||||
preview="Test Email"
|
||||
>
|
||||
<Text
|
||||
className="text-base font-light leading-8 text-green-800 "
|
||||
>
|
||||
Hi, this is a test email for settings and alerts !
|
||||
</Text>
|
||||
<Text
|
||||
className="text-base font-light leading-8 text-green-800 ">
|
||||
Soluce Technologies | Portabase
|
||||
</Text>
|
||||
</EmailLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default TestEmailSettings;
|
||||
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
Text as ReactEmailText
|
||||
} from "@react-email/components";
|
||||
import {ComponentPropsWithoutRef} from "react";
|
||||
|
||||
export const Text = (props: ComponentPropsWithoutRef<typeof ReactEmailText>) => {
|
||||
return (
|
||||
<ReactEmailText
|
||||
className="text-base font-light leading-8 text-green-800 "
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -31,6 +31,7 @@ function checkRouteExists(pathname) {
|
||||
// /^\/api\/auth\/\w+$/, // Dynamic route with a number as a parameter (e.g., /api/dynamic/123)
|
||||
// /^\/api\/agent\/healthcheck\/\w+$/, // Dynamic route with an alphanumeric parameter (e.g., /api/user/username)
|
||||
/^\/api\/agent\/[^/]+\/status\/?$/, // Dynamic route for /api/agent/[id]/status
|
||||
/^\/api\/logs\$/,
|
||||
];
|
||||
return routePatterns.some(pattern => pattern.test(pathname));
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@react-email/components": "^0.0.28",
|
||||
"@t3-oss/env-nextjs": "^0.11.1",
|
||||
"@tanstack/react-query": "^5.59.18",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
@@ -49,6 +50,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dockerode": "^4.0.2",
|
||||
"embla-carousel-react": "^8.3.1",
|
||||
"input-otp": "^1.4.0",
|
||||
"lucide-react": "^0.454.0",
|
||||
@@ -58,9 +60,11 @@
|
||||
"next-intl": "^3.24.0",
|
||||
"next-safe-action": "^7.4.3",
|
||||
"next-themes": "^0.3.0",
|
||||
"nodemailer": "^6.9.16",
|
||||
"react": "19.0.0-rc-66855b96-20241106",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||
"react-email": "^3.0.2",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-resizable-panels": "^2.1.6",
|
||||
"react-twc": "^1.4.2",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client"
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {useState} from "react";
|
||||
import {Loader2} from "lucide-react";
|
||||
|
||||
export type VariantButton = {
|
||||
secondary: string
|
||||
default: string
|
||||
outline: string
|
||||
ghost: string
|
||||
link: string
|
||||
destructive: string
|
||||
}
|
||||
|
||||
export type ButtonWithConfirmProps = {
|
||||
icon?: any,
|
||||
text: string,
|
||||
variant?: keyof VariantButton ,
|
||||
className?: string,
|
||||
onClick: () => void,
|
||||
isPending? : boolean
|
||||
};
|
||||
|
||||
export const ButtonWithLoading = (props: ButtonWithConfirmProps) => {
|
||||
return(
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.onClick()
|
||||
}}
|
||||
variant={props.variant ? props.variant : "default"}
|
||||
className={props.className}
|
||||
>
|
||||
{props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>}
|
||||
{props.text}
|
||||
<>
|
||||
{props.icon ? props.icon : null}
|
||||
</>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
+101
-4
@@ -14,26 +14,50 @@ import {
|
||||
EmailFormSchema,
|
||||
EmailFormType
|
||||
} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/email-form.schema";
|
||||
import Link from "next/link";
|
||||
import {PasswordInput} from "@/components/wrappers/Auth/PaswordInput/password-input";
|
||||
import {
|
||||
updateEmailSettingsAction
|
||||
} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/email-form.action";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
export type EmailFormProps = {
|
||||
defaultValues?: EmailFormType;
|
||||
}
|
||||
|
||||
export const EmailForm = (props: EmailFormProps) => {
|
||||
|
||||
const isCreate = !Boolean(props.defaultValues)
|
||||
|
||||
const form = useZodForm({
|
||||
schema: EmailFormSchema,
|
||||
defaultValues: props.defaultValues,
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: EmailFormType) => {
|
||||
|
||||
const updateEmailSettings = await updateEmailSettingsAction({name: "system", data: values})
|
||||
const data = updateEmailSettings?.data?.data
|
||||
if (updateEmailSettings?.serverError || !data) {
|
||||
console.log(updateEmailSettings?.serverError);
|
||||
toast.error(updateEmailSettings?.serverError);
|
||||
return;
|
||||
}
|
||||
toast.success(`Success updating email informations`);
|
||||
router.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<CardContent>
|
||||
|
||||
<Form form={form}
|
||||
|
||||
className="flex flex-col gap-4 mt-3"
|
||||
onSubmit={async (values) => {
|
||||
await mutation.mutateAsync(values);
|
||||
@@ -42,6 +66,8 @@ export const EmailForm = (props: EmailFormProps) => {
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtpFrom"
|
||||
defaultValue=""
|
||||
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>From Email *</FormLabel>
|
||||
@@ -54,10 +80,81 @@ export const EmailForm = (props: EmailFormProps) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtpHost"
|
||||
defaultValue=""
|
||||
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server Host *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"ssl0.ovh.net"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Your email server host"}</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtpPort"
|
||||
defaultValue=""
|
||||
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server Port *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"465"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Your email server port (send)"}</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtpPassword"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder="Password" {...field}/>
|
||||
</FormControl>
|
||||
<FormDescription>{"Your email server password"}</FormDescription>
|
||||
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="smtpUser"
|
||||
defaultValue=""
|
||||
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>User Email *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"exemple@portabase.com"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"The email server user"}</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end gap-4">
|
||||
|
||||
<Button>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button>
|
||||
Save
|
||||
</Button>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
"use server"
|
||||
import {userAction} from "@/safe-actions";
|
||||
import {z} from "zod";
|
||||
import {prisma} from "@/prisma";
|
||||
import {EmailFormSchema} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/email-form.schema";
|
||||
|
||||
|
||||
export const updateEmailSettingsAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
data: EmailFormSchema,
|
||||
}
|
||||
)
|
||||
)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
|
||||
console.log("parsedInput", parsedInput.data)
|
||||
|
||||
const updatedSettings = await prisma.settings.update({
|
||||
where: {
|
||||
name: parsedInput.name,
|
||||
},
|
||||
data: parsedInput.data,
|
||||
})
|
||||
|
||||
return {
|
||||
data: updatedSettings,
|
||||
|
||||
}
|
||||
})
|
||||
@@ -1,12 +1,60 @@
|
||||
import {EmailForm} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/EmailForm";
|
||||
import {Settings} from "@prisma/client";
|
||||
import {Send} from "lucide-react";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/Button/ButtonWithLoading/ButtonWithLoading";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {sendEmail} from "@/utils/email-helper";
|
||||
import TestEmailSettings from "../../../../../../emails/TestEmailSettings";
|
||||
import {render} from "@react-email/render";
|
||||
import {toast} from "sonner";
|
||||
|
||||
|
||||
export type SettingsEmailTabProps = {}
|
||||
|
||||
export const SettingsEmailTab = (props:SettingsEmailTabProps) => {
|
||||
return(
|
||||
<div>
|
||||
<EmailForm/>
|
||||
export type SettingsEmailTabProps = {
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
export const SettingsEmailTab = (props: SettingsEmailTabProps) => {
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const email = await sendEmail({
|
||||
to: props.settings.smtpUser,
|
||||
subject: "Portabase email test !",
|
||||
html: await render(TestEmailSettings(), {})
|
||||
});
|
||||
if(email.response){
|
||||
toast.success("Test Email Successfully sent !");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const handleSendMailTest = async () => {
|
||||
console.log("Sending email test...");
|
||||
await mutation.mutateAsync()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full py-4">
|
||||
<div className="flex gap-4 h-fit justify-between">
|
||||
<h1>Settings for Portabase storage</h1>
|
||||
{props.settings.smtpFrom && (
|
||||
<ButtonWithLoading
|
||||
isPending={mutation.isPending}
|
||||
onClick={async () => {
|
||||
await handleSendMailTest()
|
||||
}}
|
||||
icon={<Send/>}
|
||||
text="Send email test"
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<EmailForm defaultValues={props.settings.smtpFrom ? props.settings : null}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -40,7 +40,7 @@ export const SettingsStorageTab = (props: SettingsStorageTabProps) => {
|
||||
</div>
|
||||
{isSwitched && (
|
||||
<div className="mt-5">
|
||||
<StorageS3Form/>
|
||||
<StorageS3Form defaultValues={props.settings.s3EndPointUrl ? props.settings : null}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+7
-3
@@ -22,6 +22,7 @@ export type S3FormProps = {
|
||||
export const StorageS3Form = (props: S3FormProps) => {
|
||||
const form = useZodForm({
|
||||
schema: S3FormSchema,
|
||||
defaultValues: props.defaultValues,
|
||||
});
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: S3FormType) => {
|
||||
@@ -99,9 +100,12 @@ export const StorageS3Form = (props: S3FormProps) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button>
|
||||
Save
|
||||
</Button>
|
||||
<div className="flex justify-end gap-4">
|
||||
|
||||
<Button>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -37,7 +37,7 @@ export const SettingsTabs = (props: SettingsTabsProps) => {
|
||||
<DataTableWithPagination columns={usersColumns} data={props.users}/>
|
||||
</TabsContent>
|
||||
<TabsContent value="email">
|
||||
<SettingsEmailTab/>
|
||||
<SettingsEmailTab settings={props.settings}/>
|
||||
</TabsContent>
|
||||
<TabsContent value="storage">
|
||||
<SettingsStorageTab settings={props.settings}/>
|
||||
|
||||
@@ -15,11 +15,11 @@ export const SidebarMenuCustom = (props: SidebarMenuCustomProps) => {
|
||||
const pathname = usePathname();
|
||||
// Menu items.
|
||||
const items = [
|
||||
{
|
||||
title: "Home",
|
||||
url: "/",
|
||||
icon: Home,
|
||||
},
|
||||
// {
|
||||
// title: "Home",
|
||||
// url: "/",
|
||||
// icon: Home,
|
||||
// },
|
||||
{
|
||||
title: "Agents",
|
||||
url: "agents",
|
||||
|
||||
+15
-5
@@ -9,21 +9,26 @@ export const env = createEnv({
|
||||
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(),
|
||||
NEXTAUTH_URL: z.string(),
|
||||
|
||||
SMTP_PASSWORD: z.string().optional(),
|
||||
SMTP_FROM: z.string().optional(),
|
||||
SMTP_HOST: z.string().optional(),
|
||||
SMTP_PORT: z.string().optional(),
|
||||
SMTP_USER: z.string().optional(),
|
||||
|
||||
AUTH_GOOGLE_ID: z.string().optional(),
|
||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||
|
||||
S3_ENDPOINT: z.string().optional(),
|
||||
S3_ACCESS_KEY: z.string().optional(),
|
||||
S3_SECRET_KEY: z.string().optional(),
|
||||
S3_BUCKET_NAME: z.string().optional(),
|
||||
S3_PORT: z.string().optional(),
|
||||
S3_USE_SSL: z.string().optional(),
|
||||
|
||||
STORAGE_TYPE: z.string().optional(),
|
||||
},
|
||||
/*
|
||||
* Environment variables available on the client (and server).
|
||||
@@ -45,18 +50,23 @@ export const env = createEnv({
|
||||
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,
|
||||
|
||||
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||
|
||||
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
|
||||
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
|
||||
S3_PORT: process.env.S3_PORT,
|
||||
S3_USE_SSL: process.env.S3_USE_SSL,
|
||||
|
||||
STORAGE_TYPE: process.env.STORAGE_TYPE,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"use server"
|
||||
import {prisma} from "@/prisma";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
|
||||
type Payload = {
|
||||
to: string;
|
||||
subject: string;
|
||||
html: any
|
||||
};
|
||||
|
||||
|
||||
export const sendEmail = async (data: Payload) => {
|
||||
|
||||
const settings = await prisma.settings.findUnique({
|
||||
where: {
|
||||
name: "system"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const smtpSettings = {
|
||||
host: settings.smtpHost,
|
||||
port: parseInt(settings.smtpPort),
|
||||
auth: {
|
||||
user: settings.smtpUser,
|
||||
pass: settings.smtpPassword,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Create a transporter object using nodemailer
|
||||
const transporter = nodemailer.createTransport({
|
||||
...smtpSettings
|
||||
});
|
||||
|
||||
// Render the React component to HTML
|
||||
// const emailHtml = await render(HelloEmail(), {});
|
||||
|
||||
// Set up email options
|
||||
const mailOptions = {
|
||||
...data
|
||||
};
|
||||
|
||||
return await transporter.sendMail({
|
||||
from: settings.smtpFrom,
|
||||
...mailOptions,
|
||||
});
|
||||
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import {env} from "@/env.mjs";
|
||||
|
||||
export const getServerUrl = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.location.origin;
|
||||
}
|
||||
return `https://${env.NEXT_PUBLIC_DOMAIN_NAME}`;
|
||||
}
|
||||
+29
-1
@@ -1,4 +1,6 @@
|
||||
import {prisma} from "@/prisma";
|
||||
import {Settings} from "@prisma/client";
|
||||
import {env} from "@/env.mjs";
|
||||
|
||||
|
||||
export function init() {
|
||||
@@ -15,19 +17,45 @@ export function init() {
|
||||
|
||||
async function createSettingsIfNotExist() {
|
||||
|
||||
const configSettings = {
|
||||
name: "system",
|
||||
smtpPassword: env.SMTP_PASSWORD ?? null,
|
||||
smtpFrom: env.SMTP_FROM ?? null,
|
||||
smtpHost: env.SMTP_HOST ?? null,
|
||||
smtpPort: env.SMTP_PORT ?? null,
|
||||
smtpUser: env.SMTP_USER ?? null,
|
||||
s3EndPointUrl: env.S3_ENDPOINT ?? null,
|
||||
s3AccessKeyId: env.S3_ACCESS_KEY ?? null,
|
||||
s3SecretAccessKey: env.S3_SECRET_KEY ?? null,
|
||||
S3BucketName: env.S3_BUCKET_NAME ?? null,
|
||||
}
|
||||
|
||||
|
||||
|
||||
const settings = await prisma.settings.findUnique({
|
||||
where: {
|
||||
name: "system",
|
||||
}
|
||||
})
|
||||
if(!settings){
|
||||
console.log("====Init Setting====")
|
||||
console.log("====Init Setting : Create ====")
|
||||
await prisma.settings.create({
|
||||
data: {
|
||||
name: "system",
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log("====Init Setting : Update ====")
|
||||
await prisma.settings.update({
|
||||
where: {
|
||||
name: "system",
|
||||
},
|
||||
data: {
|
||||
...configSettings,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function consoleAscii(){
|
||||
|
||||
Reference in New Issue
Block a user