diff --git a/src/app/groups/layout.tsx b/src/app/groups/layout.tsx index dadd3a1..75128c8 100644 --- a/src/app/groups/layout.tsx +++ b/src/app/groups/layout.tsx @@ -1,4 +1,8 @@ -import { FeedbackButton } from '@/components/feedback-button/feedback-button' +import { + FeedbackButton, + FeedbackModal, +} from '@/components/feedback-button/feedback-button' +import { env } from '@/lib/env' import { PropsWithChildren } from 'react' export default function GroupsLayout({ children }: PropsWithChildren<{}>) { @@ -7,7 +11,9 @@ export default function GroupsLayout({ children }: PropsWithChildren<{}>) {
{children}
- + + + ) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 56fed4f..bff432c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,11 @@ -import { DonationButton } from '@/components/donation-button' +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' @@ -141,13 +142,19 @@ export default function RootLayout({ contributors + + + + + - {env.STRIPE_DONATION_LINK && ( -
- -
- )} diff --git a/src/components/donation-button.tsx b/src/components/donation-button.tsx deleted file mode 100644 index b2c360c..0000000 --- a/src/components/donation-button.tsx +++ /dev/null @@ -1,126 +0,0 @@ -'use client' -import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog' -import { - Drawer, - DrawerContent, - DrawerDescription, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from '@/components/ui/drawer' -import { useMediaQuery } from '@/lib/hooks' -import { Heart } from 'lucide-react' -import { useState } from 'react' - -type Props = { - donationUrl: string -} - -export function DonationButton({ donationUrl }: Props) { - const isDesktop = useMediaQuery('(min-width: 768px)') - return isDesktop ? ( - - ) : ( - - ) -} - -function DonationDrawer({ donationUrl }: Props) { - const [open, setOpen] = useState(false) - - return ( - - - - - - - Support us - - Help keep Spliit free and without ads! - - -
- -
-
-
- ) -} - -function DonationDialog({ donationUrl }: Props) { - const [open, setOpen] = useState(false) - - return ( - - - - - - - Support us - - Help keep Spliit free and without ads! - - - - - - ) -} - -function DonationForm({ donationUrl }: Props) { - return ( - <> -
-

- 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. -

-

By supporting Spliit:

-
    -
  • - You contribute to the hosting costs for the app - (currently ~$150/year). -
  • -
  • - You help us keeping the application{' '} - free and without ads. -
  • -
  • - You give me energy to build new features and - improve the application. -
  • -
-

- You will be redirected to Stripe, our payment - provider, where you can choose an amount to donate and complete the - payment. -

-
-
- -
- - ) -} diff --git a/src/components/feedback-button/feedback-button-actions.tsx b/src/components/feedback-button/feedback-button-actions.tsx new file mode 100644 index 0000000..00c8748 --- /dev/null +++ b/src/components/feedback-button/feedback-button-actions.tsx @@ -0,0 +1,23 @@ +'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: , + }) +} diff --git a/src/components/feedback-button/feedback-button-client.tsx b/src/components/feedback-button/feedback-button-client.tsx deleted file mode 100644 index 96a1758..0000000 --- a/src/components/feedback-button/feedback-button-client.tsx +++ /dev/null @@ -1,206 +0,0 @@ -'use client' -import { formSchema } from '@/components/feedback-button/feedback-button-common' -import { Button } from '@/components/ui/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog' -import { - Drawer, - DrawerContent, - DrawerDescription, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from '@/components/ui/drawer' -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form' -import { Input } from '@/components/ui/input' -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 { Loader2, MessageCircle } from 'lucide-react' -import { useState } from 'react' -import { useForm } from 'react-hook-form' -import * as z from 'zod' - -type FormValues = z.infer - -type Props = { - sendFeedback: (values: FormValues) => Promise -} - -export function FeedbackButtonClient({ sendFeedback }: Props) { - const { toast } = useToast() - const isDesktop = useMediaQuery('(min-width: 768px)') - - 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.', - }) - } - - return ( -
- {isDesktop ? ( - - ) : ( - - )} -
- ) -} - -function FeedbackDrawer({ - onSubmit, -}: { - onSubmit: (values: FormValues) => Promise -}) { - const [open, setOpen] = useState(false) - - return ( -
- - - - - - - Give us your feedback! - - We are always working to improve the user experience, and your - feedback helps us a lot. - - -
- { - await onSubmit(values) - setOpen(false) - }} - /> -
-
-
-
- ) -} - -function FeedbackDialog({ - onSubmit, -}: { - onSubmit: (values: FormValues) => Promise -}) { - const [open, setOpen] = useState(false) - - return ( -
- - - - - - - Give us your feedback! - - We are always working to improve the user experience, and your - feedback helps us a lot. - - - { - await onSubmit(values) - setOpen(false) - }} - /> - - -
- ) -} - -function FeedbackForm({ - onSubmit, -}: { - onSubmit: (values: FormValues) => Promise -}) { - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { email: '', message: '' }, - }) - - const isSubmitting = form.formState.isSubmitting - return ( -
- -
- ( - - Your email address - - - - - Optional. Provide it if you want us to get back to you. - - - - )} - /> - ( - - Your feedback - -