mirror of
https://github.com/spliit-app/spliit.git
synced 2025-12-06 09:29:39 +01:00
Clean project from marketing content
This commit is contained in:
@@ -1,9 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
remotePatterns: [{ hostname: 'avatars.githubusercontent.com' }],
|
||||
},
|
||||
}
|
||||
const nextConfig = {}
|
||||
|
||||
const { withPlausibleProxy } = require('next-plausible')
|
||||
module.exports = withPlausibleProxy()(nextConfig)
|
||||
module.exports = nextConfig
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import {
|
||||
FeedbackButton,
|
||||
FeedbackModal,
|
||||
} from '@/components/feedback-button/feedback-button'
|
||||
import { env } from '@/lib/env'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
export default function GroupsLayout({ children }: PropsWithChildren<{}>) {
|
||||
@@ -11,9 +6,6 @@ export default function GroupsLayout({ children }: PropsWithChildren<{}>) {
|
||||
<main className="flex-1 max-w-screen-md w-full mx-auto px-4 py-6 flex flex-col gap-6">
|
||||
{children}
|
||||
</main>
|
||||
<FeedbackModal donationUrl={env.STRIPE_DONATION_LINK}>
|
||||
<FeedbackButton />
|
||||
</FeedbackModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { FeedbackModal } from '@/components/feedback-button/feedback-button'
|
||||
import { ProgressBar } from '@/components/progress-bar'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { ThemeToggle } from '@/components/theme-toggle'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import { env } from '@/lib/env'
|
||||
import { HeartFilledIcon } from '@radix-ui/react-icons'
|
||||
import type { Metadata, Viewport } from 'next'
|
||||
import PlausibleProvider from 'next-plausible'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import './globals.css'
|
||||
@@ -67,9 +64,6 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
{env.PLAUSIBLE_DOMAIN && (
|
||||
<PlausibleProvider domain={env.PLAUSIBLE_DOMAIN} trackOutboundLinks />
|
||||
)}
|
||||
<body className="pt-16 min-h-[100dvh] flex flex-col items-stretch bg-slate-50 bg-opacity-30 dark:bg-background">
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
@@ -111,7 +105,7 @@ export default function RootLayout({
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{children}
|
||||
<div className="flex-1 flex flex-col">{children}</div>
|
||||
|
||||
<footer className="sm:p-8 md:p-16 sm:mt-16 sm:text-sm md:text-base md:mt-32 bg-slate-50 dark:bg-card border-t p-6 mt-8 flex flex-col sm:flex-row sm:justify-between gap-4 text-xs [&_a]:underline">
|
||||
<div className="flex flex-col space-y-2">
|
||||
@@ -142,17 +136,6 @@ export default function RootLayout({
|
||||
contributors
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<FeedbackModal
|
||||
donationUrl={env.STRIPE_DONATION_LINK}
|
||||
defaultTab="support"
|
||||
>
|
||||
<Button variant="link" className="text-pink-600 -mx-4">
|
||||
<HeartFilledIcon className="w-4 h-4 mr-2" />
|
||||
Support us
|
||||
</Button>
|
||||
</FeedbackModal>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
174
src/app/page.tsx
174
src/app/page.tsx
@@ -1,17 +1,5 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
BarChartHorizontalBig,
|
||||
CircleDollarSign,
|
||||
Divide,
|
||||
FolderTree,
|
||||
Github,
|
||||
List,
|
||||
LucideIcon,
|
||||
Share,
|
||||
ShieldX,
|
||||
Users,
|
||||
} from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import { Github, LucideIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
@@ -28,164 +16,18 @@ export default function HomePage() {
|
||||
& <strong>Family</strong>
|
||||
</h1>
|
||||
<p className="max-w-[42rem] leading-normal text-muted-foreground sm:text-xl sm:leading-8">
|
||||
No ads. No account. <br className="sm:hidden" /> Open Source.
|
||||
Forever Free.
|
||||
Welcome to your new <strong>Spliit</strong> instance! <br />
|
||||
Customize this page by editing <em>src/app/page.tsx</em>.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button asChild size="lg">
|
||||
<Link
|
||||
className="inline-flex items-center justify-center text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background bg-primary text-primary-foreground hover:bg-primary/90 h-11 px-8 rounded-md"
|
||||
href="/groups/create"
|
||||
>
|
||||
Create a group
|
||||
</Link>
|
||||
<Button asChild>
|
||||
<Link href="/groups">Go to groups</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-slate-50 dark:bg-card py-16 md:py-24 lg:py-32">
|
||||
<div className="p-4 flex mx-auto max-w-screen-md flex-col items-center text-center">
|
||||
<h2 className="font-bold text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
|
||||
Features
|
||||
</h2>
|
||||
<p
|
||||
className="mt-2 md:mt-3 leading-normal text-muted-foreground sm:text-lg sm:leading-7"
|
||||
style={{ textWrap: 'balance' } as any}
|
||||
>
|
||||
Spliit is a minimalist application to track and share expenses with
|
||||
your friends and family.
|
||||
</p>
|
||||
<div className="mt-8 md:mt-6 w-full grid grid-cols-2 sm:grid-cols-3 gap-2 sm:gap-4 text-left">
|
||||
<Feature
|
||||
Icon={Users}
|
||||
name="Groups"
|
||||
description="Create a group for a travel, an event, a gift…"
|
||||
/>
|
||||
<Feature
|
||||
Icon={List}
|
||||
name="Expenses"
|
||||
description="Create and list expenses in your group."
|
||||
/>
|
||||
<Feature
|
||||
Icon={FolderTree}
|
||||
name="Categories"
|
||||
description="Assign categories to your expenses."
|
||||
/>
|
||||
<Feature
|
||||
Icon={Divide}
|
||||
name="Advanced split"
|
||||
description="Split expenses by percentage, shares or amount."
|
||||
/>
|
||||
<Feature
|
||||
Icon={Share}
|
||||
name="Share"
|
||||
description="Send the group link to participants."
|
||||
/>
|
||||
<Feature
|
||||
Icon={BarChartHorizontalBig}
|
||||
name="Balances"
|
||||
description="Visualize how much each participant spent."
|
||||
/>
|
||||
<Feature
|
||||
Icon={CircleDollarSign}
|
||||
name="Reimbursements"
|
||||
description="Optimize money transfers between participants."
|
||||
/>
|
||||
<Feature
|
||||
Icon={ShieldX}
|
||||
name="No ads"
|
||||
description="No account. No limitation. No problem."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-16 md:py-24 lg:py-32">
|
||||
<div className="container flex max-w-screen-md flex-col items-center text-center">
|
||||
<h2 className="font-bold text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
|
||||
Proudly Open Source
|
||||
</h2>
|
||||
<p
|
||||
className="mt-2 leading-normal text-muted-foreground sm:text-lg sm:leading-7"
|
||||
style={{ textWrap: 'balance' } as any}
|
||||
>
|
||||
Spliit is open source and lives thanks to amazing{' '}
|
||||
<a
|
||||
className="underline"
|
||||
target="_blank"
|
||||
href="https://github.com/scastiel/spliit2/graphs/contributors"
|
||||
>
|
||||
contributors
|
||||
</a>
|
||||
!
|
||||
</p>
|
||||
<ul className="flex gap-4 mt-6">
|
||||
{[
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/301948?s=120&v=4',
|
||||
user: 'scastiel',
|
||||
name: 'Sebastien Castiel',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/3932568?s=120&v=4',
|
||||
user: 'ChristopherJohnston',
|
||||
name: 'Chris Johnston',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/11523186?s=120&v=4',
|
||||
user: 'acuteengle',
|
||||
name: 'Brandon Eng',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/24687853?s=120&v=4',
|
||||
user: 'Max-TheCat',
|
||||
name: 'Max',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/10518723?s=120&v=4',
|
||||
user: 'ankitbahl',
|
||||
name: 'Ankit Bahl',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://avatars.githubusercontent.com/u/13032812?s=120&v=4',
|
||||
user: '174n',
|
||||
name: 'Ivan Alexandrov',
|
||||
},
|
||||
].map((contributor) => (
|
||||
<li key={contributor.user}>
|
||||
<a
|
||||
href={`https://github.com/${contributor.user}`}
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
>
|
||||
<Image
|
||||
src={contributor.avatar}
|
||||
width={60}
|
||||
height={60}
|
||||
alt={contributor.user}
|
||||
className="rounded-full border hover:scale-110 transition-transform"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 md:mt-6">
|
||||
<Button asChild variant="secondary" size="lg">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://github.com/scastiel/spliit2"
|
||||
>
|
||||
<Button asChild variant="secondary">
|
||||
<Link href="https://github.com/spliit-app/spliit">
|
||||
<Github className="w-4 h-4 mr-2" />
|
||||
GitHub
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
'use server'
|
||||
import { formSchema } from '@/components/feedback-button/feedback-button-common'
|
||||
import { FeedbackButtonEmail } from '@/components/feedback-button/feedback-button-email'
|
||||
import { getResend } from '@/lib/resend'
|
||||
import { env } from 'process'
|
||||
|
||||
export async function sendFeedback(values: unknown) {
|
||||
'use server'
|
||||
const { email, message } = formSchema.parse(values)
|
||||
const resend = getResend()
|
||||
if (!resend || !env.FEEDBACK_EMAIL_FROM || !env.FEEDBACK_EMAIL_TO) {
|
||||
console.warn(
|
||||
'Resend is not properly configured. Feedback email won’t be sent.',
|
||||
)
|
||||
return
|
||||
}
|
||||
await resend.emails.send({
|
||||
from: env.FEEDBACK_EMAIL_FROM,
|
||||
to: env.FEEDBACK_EMAIL_TO,
|
||||
subject: `Spliit: new feedback from ${email || 'anonymous user'}`,
|
||||
react: <FeedbackButtonEmail email={email} message={message} />,
|
||||
})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const formSchema = z.object({
|
||||
email: z.union([
|
||||
z.string().email('Please enter a valid email address.'),
|
||||
z.string().max(0),
|
||||
]),
|
||||
message: z.string().min(10, 'Please enter at least 10 characters.').max(5000),
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Heading } from '@react-email/heading'
|
||||
import { Html } from '@react-email/html'
|
||||
import { Preview } from '@react-email/preview'
|
||||
import { Text } from '@react-email/text'
|
||||
|
||||
type Props = {
|
||||
email?: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export function FeedbackButtonEmail({ email, message }: Props) {
|
||||
return (
|
||||
<Html>
|
||||
<Preview>New feedback from {email || 'anonymous user'}</Preview>
|
||||
<Heading>New feedback on Spliit</Heading>
|
||||
<Text>
|
||||
Email address: <strong>{email || 'not provided'}</strong>
|
||||
</Text>
|
||||
<pre style={{ padding: 16, borderLeft: '2px solid lightgray' }}>
|
||||
{message}
|
||||
</pre>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
'use client'
|
||||
import { sendFeedback } from '@/components/feedback-button/feedback-button-actions'
|
||||
import { formSchema } from '@/components/feedback-button/feedback-button-common'
|
||||
import { Button, ButtonProps } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { useMediaQuery } from '@/lib/hooks'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Heart, HeartIcon, Loader2, MessageCircle } from 'lucide-react'
|
||||
import { PropsWithChildren, ReactNode, SetStateAction, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>
|
||||
|
||||
type Props = {
|
||||
donationUrl: string
|
||||
defaultTab?: 'feedback' | 'support'
|
||||
}
|
||||
|
||||
export function FeedbackModal({
|
||||
donationUrl,
|
||||
defaultTab = 'feedback',
|
||||
children,
|
||||
}: PropsWithChildren<Props>) {
|
||||
const { toast } = useToast()
|
||||
const isDesktop = useMediaQuery('(min-width: 640px)')
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
async function onSubmit(values: FormValues) {
|
||||
await sendFeedback(values)
|
||||
toast({
|
||||
title: 'Thank you for your feedback!',
|
||||
description:
|
||||
'We will have a look at it as soon as possible, and will get back to you if needed.',
|
||||
})
|
||||
}
|
||||
|
||||
const Wrapper = isDesktop ? FeedbackDialog : FeedbackDrawer
|
||||
|
||||
return (
|
||||
<Wrapper open={open} setOpen={setOpen} button={children}>
|
||||
<FeedbackContent
|
||||
onSubmit={async (values) => {
|
||||
await onSubmit(values)
|
||||
setOpen(false)
|
||||
}}
|
||||
donationUrl={donationUrl}
|
||||
defaultTab={defaultTab}
|
||||
/>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedbackDrawer({
|
||||
children,
|
||||
open,
|
||||
setOpen,
|
||||
button,
|
||||
}: PropsWithChildren<{
|
||||
open: boolean
|
||||
setOpen: (open: SetStateAction<boolean>) => void
|
||||
button: ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>{button}</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<div className="p-4">{children}</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedbackDialog({
|
||||
children,
|
||||
open,
|
||||
setOpen,
|
||||
button,
|
||||
}: PropsWithChildren<{
|
||||
open: boolean
|
||||
setOpen: (open: SetStateAction<boolean>) => void
|
||||
button: ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{button}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<div className="pt-4">{children}</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedbackContent({
|
||||
onSubmit,
|
||||
donationUrl,
|
||||
defaultTab,
|
||||
}: {
|
||||
onSubmit: (values: FormValues) => Promise<void>
|
||||
donationUrl: string
|
||||
defaultTab: 'feedback' | 'support'
|
||||
}) {
|
||||
return (
|
||||
<Tabs defaultValue={defaultTab}>
|
||||
<div className="mt-2 mb-6">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="feedback">Give feedback</TabsTrigger>
|
||||
<TabsTrigger value="support">Support us</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<TabsContent value="feedback">
|
||||
<FeedbackForm onSubmit={onSubmit} />
|
||||
</TabsContent>
|
||||
<TabsContent value="support">
|
||||
<DonationForm donationUrl={donationUrl} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedbackForm({
|
||||
onSubmit,
|
||||
}: {
|
||||
onSubmit: (values: FormValues) => Promise<void>
|
||||
}) {
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { email: '', message: '' },
|
||||
})
|
||||
|
||||
const isSubmitting = form.formState.isSubmitting
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold leading-none tracking-tight pb-1.5">
|
||||
Give us your feedback
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We are always working to improve the user experience, and your
|
||||
feedback helps us a lot.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Your email address</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="your@email.com"
|
||||
className="text-base"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Optional. Provide it if you want us to get back to you.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="message"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Your feedback</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Enter your feedback"
|
||||
className="text-base"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center mt-1">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> Submitting…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MessageCircle className="w-4 h-4 mr-2" /> Send
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
function DonationForm({ donationUrl }: { donationUrl: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold leading-none tracking-tight pb-1.5">
|
||||
Support us
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Help keep <strong>Spliit</strong> free and without ads!
|
||||
</p>
|
||||
</div>
|
||||
<div className="prose prose-sm dark:prose-invert">
|
||||
<p>
|
||||
Spliit is offered for free, but costs money and energy. If you like
|
||||
the app, you can choose to support it by buying me (Sebastien) a
|
||||
coffee with a one-time small donation.
|
||||
</p>
|
||||
<p>By supporting Spliit:</p>
|
||||
<ul>
|
||||
<li>
|
||||
You contribute to the <strong>hosting costs</strong> for the app
|
||||
(currently ~$150/year).
|
||||
</li>
|
||||
<li>
|
||||
You help us keeping the application{' '}
|
||||
<strong>free and without ads</strong>.
|
||||
</li>
|
||||
<li>
|
||||
You give me energy to build <strong>new features</strong> and
|
||||
improve the application.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
You will be redirected to <strong>Stripe</strong>, our payment
|
||||
provider, where you can choose an amount to donate and complete the
|
||||
payment.
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Button
|
||||
asChild
|
||||
className="bg-pink-700 hover:bg-pink-600 dark:bg-pink-500 dark:hover:bg-pink-600"
|
||||
>
|
||||
<a href={donationUrl} target="_blank">
|
||||
<Heart className="w-4 h-4 mr-2" /> Support us
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FeedbackButton({ ...props }: ButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
className="bg-pink-700 hover:bg-pink-600 dark:bg-pink-500 dark:hover:bg-pink-600 fixed right-0 bottom-4 rounded-r-none gap-2"
|
||||
{...props}
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" />
|
||||
<HeartIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -4,11 +4,6 @@ const envSchema = z.object({
|
||||
NEXT_PUBLIC_BASE_URL: z.string().url(),
|
||||
POSTGRES_URL_NON_POOLING: z.string().url(),
|
||||
POSTGRES_PRISMA_URL: z.string().url(),
|
||||
PLAUSIBLE_DOMAIN: z.string().optional(),
|
||||
FEEDBACK_EMAIL_FROM: z.string().email().optional(),
|
||||
FEEDBACK_EMAIL_TO: z.string().email().optional(),
|
||||
RESEND_API_KEY: z.string().optional(),
|
||||
STRIPE_DONATION_LINK: z.string().optional().default('https://example.com'),
|
||||
})
|
||||
|
||||
export const env = envSchema.parse(process.env)
|
||||
|
||||
Reference in New Issue
Block a user