mirror of
https://github.com/spliit-app/spliit.git
synced 2025-12-06 01:19:29 +01:00
Add donation button (closes #40)
This commit is contained in:
@@ -32,6 +32,8 @@ Spliit is a free and open source alternative to Splitwise. I created it back in
|
|||||||
|
|
||||||
The project is open to contributions. Feel free to open an issue or even a pull-request!
|
The project is open to contributions. Feel free to open an issue or even a pull-request!
|
||||||
|
|
||||||
|
If you want to contribute financially and help us keep the application free and without ads, you can also [make a small one-time donation](https://donate.stripe.com/28o3eh96G7hH8k89Ba) ❤️.
|
||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
1. Clone the repository (or fork it if you intend to contribute)
|
1. Clone the repository (or fork it if you intend to contribute)
|
||||||
|
|||||||
38
package-lock.json
generated
38
package-lock.json
generated
@@ -28,6 +28,7 @@
|
|||||||
"@react-email/html": "^0.0.7",
|
"@react-email/html": "^0.0.7",
|
||||||
"@react-email/preview": "^0.0.8",
|
"@react-email/preview": "^0.0.8",
|
||||||
"@react-email/text": "^0.0.7",
|
"@react-email/text": "^0.0.7",
|
||||||
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"lucide-react": "^0.290.0",
|
"lucide-react": "^0.290.0",
|
||||||
@@ -1628,6 +1629,32 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/typography": {
|
||||||
|
"version": "0.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
|
||||||
|
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.castarray": "^4.4.0",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"postcss-selector-parser": "6.0.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.0.0 || insiders"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@total-typescript/ts-reset": {
|
"node_modules/@total-typescript/ts-reset": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.5.1.tgz",
|
||||||
@@ -4692,11 +4719,20 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.castarray": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/loose-envify": {
|
"node_modules/loose-envify": {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"@react-email/html": "^0.0.7",
|
"@react-email/html": "^0.0.7",
|
||||||
"@react-email/preview": "^0.0.8",
|
"@react-email/preview": "^0.0.8",
|
||||||
"@react-email/text": "^0.0.7",
|
"@react-email/text": "^0.0.7",
|
||||||
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"lucide-react": "^0.290.0",
|
"lucide-react": "^0.290.0",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DonationButton } from '@/components/donation-button'
|
||||||
import { ProgressBar } from '@/components/progress-bar'
|
import { ProgressBar } from '@/components/progress-bar'
|
||||||
import { ThemeProvider } from '@/components/theme-provider'
|
import { ThemeProvider } from '@/components/theme-provider'
|
||||||
import { ThemeToggle } from '@/components/theme-toggle'
|
import { ThemeToggle } from '@/components/theme-toggle'
|
||||||
@@ -111,35 +112,42 @@ export default function RootLayout({
|
|||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<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 space-y-2 text-xs [&_a]:underline">
|
<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="sm:text-lg font-semibold text-base flex space-x-2 items-center">
|
<div className="flex flex-col space-y-2">
|
||||||
<Link className="flex items-center gap-2" href="/">
|
<div className="sm:text-lg font-semibold text-base flex space-x-2 items-center">
|
||||||
<Image
|
<Link className="flex items-center gap-2" href="/">
|
||||||
src="/logo-with-text.png"
|
<Image
|
||||||
className="m-1 h-auto"
|
src="/logo-with-text.png"
|
||||||
width={(35 * 522) / 180}
|
className="m-1 h-auto"
|
||||||
height={35}
|
width={(35 * 522) / 180}
|
||||||
alt="Spliit"
|
height={35}
|
||||||
/>
|
alt="Spliit"
|
||||||
</Link>
|
/>
|
||||||
</div>
|
</Link>
|
||||||
<div className="flex flex-col space-y a--no-underline-text-white">
|
</div>
|
||||||
<span>Made in Montréal, Québec 🇨🇦</span>
|
<div className="flex flex-col space-y a--no-underline-text-white">
|
||||||
<span>
|
<span>Made in Montréal, Québec 🇨🇦</span>
|
||||||
Built by{' '}
|
<span>
|
||||||
<a href="https://scastiel.dev" target="_blank" rel="noopener">
|
Built by{' '}
|
||||||
Sebastien Castiel
|
<a href="https://scastiel.dev" target="_blank" rel="noopener">
|
||||||
</a>{' '}
|
Sebastien Castiel
|
||||||
and{' '}
|
</a>{' '}
|
||||||
<a
|
and{' '}
|
||||||
href="https://github.com/scastiel/spliit2/graphs/contributors"
|
<a
|
||||||
target="_blank"
|
href="https://github.com/scastiel/spliit2/graphs/contributors"
|
||||||
rel="noopener"
|
target="_blank"
|
||||||
>
|
rel="noopener"
|
||||||
contributors
|
>
|
||||||
</a>
|
contributors
|
||||||
</span>
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{env.STRIPE_DONATION_LINK && (
|
||||||
|
<div>
|
||||||
|
<DonationButton donationUrl={env.STRIPE_DONATION_LINK} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
123
src/components/donation-button.tsx
Normal file
123
src/components/donation-button.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
'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 ? (
|
||||||
|
<DonationDialog donationUrl={donationUrl} />
|
||||||
|
) : (
|
||||||
|
<DonationDrawer donationUrl={donationUrl} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DonationDrawer({ donationUrl }: Props) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
|
<DrawerTrigger asChild>
|
||||||
|
<Button className="bg-pink-700 hover:bg-pink-600">
|
||||||
|
<Heart className="w-4 h-4 mr-2" /> Support us
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Support us</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Help keep <strong>Spliit</strong> free and without ads!
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="px-4 pb-4">
|
||||||
|
<DonationForm donationUrl={donationUrl} />
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DonationDialog({ donationUrl }: Props) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button className="bg-pink-700 hover:bg-pink-600">
|
||||||
|
<Heart className="w-4 h-4 mr-2" /> Support us
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Support us</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Help keep <strong>Spliit</strong> free and without ads!
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DonationForm donationUrl={donationUrl} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DonationForm({ donationUrl }: Props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="prose prose-sm">
|
||||||
|
<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="mt-4 text-center">
|
||||||
|
<Button asChild className="bg-pink-700 hover:bg-pink-600">
|
||||||
|
<a href={donationUrl} target="_blank">
|
||||||
|
<Heart className="w-4 h-4 mr-2" /> Support us
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ const envSchema = z.object({
|
|||||||
FEEDBACK_EMAIL_FROM: z.string().email().optional(),
|
FEEDBACK_EMAIL_FROM: z.string().email().optional(),
|
||||||
FEEDBACK_EMAIL_TO: z.string().email().optional(),
|
FEEDBACK_EMAIL_TO: z.string().email().optional(),
|
||||||
RESEND_API_KEY: z.string().optional(),
|
RESEND_API_KEY: z.string().optional(),
|
||||||
|
STRIPE_DONATION_LINK: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const env = envSchema.parse(process.env)
|
export const env = envSchema.parse(process.env)
|
||||||
|
|||||||
@@ -89,5 +89,5 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('tailwindcss-animate')],
|
plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user