2024-08-06 10:48:06 +02:00
|
|
|
<script lang="ts">
|
|
|
|
import IconCalendarRepeat from "@tabler/icons-svelte/IconCalendarRepeat.svelte";
|
2024-08-06 12:45:25 +02:00
|
|
|
import IconCoin from "@tabler/icons-svelte/IconCoin.svelte";
|
2024-08-06 10:48:06 +02:00
|
|
|
import IconArrowRight from "@tabler/icons-svelte/IconArrowRight.svelte";
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
import IconCup from "@tabler/icons-svelte/IconCup.svelte";
|
|
|
|
import IconPizza from "@tabler/icons-svelte/IconPizza.svelte";
|
|
|
|
import IconToolsKitchen2 from "@tabler/icons-svelte/IconToolsKitchen2.svelte";
|
|
|
|
|
|
|
|
import DonationOption from "$components/donate/DonationOption.svelte";
|
|
|
|
|
2024-08-06 10:48:06 +02:00
|
|
|
import { donate } from "$lib/env";
|
|
|
|
|
2024-08-06 20:28:54 +02:00
|
|
|
let customInput: HTMLInputElement;
|
2024-08-06 20:26:53 +02:00
|
|
|
let customInputValue: number | null;
|
2024-08-06 20:29:39 +02:00
|
|
|
let customFocused = false;
|
2024-08-06 10:48:06 +02:00
|
|
|
|
2024-08-06 20:26:53 +02:00
|
|
|
type Processor = "stripe" | "liberapay";
|
|
|
|
let processor: Processor = "stripe";
|
2024-08-06 10:48:06 +02:00
|
|
|
|
2024-08-06 20:26:53 +02:00
|
|
|
const donationMethods: Record<Processor, (_: number) => void> = {
|
|
|
|
stripe: (amount: number) => {
|
|
|
|
const url = new URL(donate.stripe);
|
|
|
|
url.searchParams.set("__prefilled_amount", amount.toString());
|
|
|
|
window.open(url, "_blank");
|
|
|
|
},
|
|
|
|
liberapay: (amount: number) => {
|
|
|
|
const url = new URL(donate.liberapay);
|
|
|
|
url.searchParams.set("currency", "USD");
|
|
|
|
url.searchParams.set("period", "monthly");
|
|
|
|
url.searchParams.set("amount", (amount / 100).toString());
|
|
|
|
window.open(url, "_blank");
|
|
|
|
}
|
2024-08-06 12:45:25 +02:00
|
|
|
};
|
2024-08-06 10:48:06 +02:00
|
|
|
|
2024-08-06 20:26:53 +02:00
|
|
|
const send = (amount: number) => {
|
|
|
|
return donationMethods[processor](amount);
|
|
|
|
}
|
2024-08-06 20:28:54 +02:00
|
|
|
|
|
|
|
const sendCustom = () => {
|
|
|
|
if (!customInput.reportValidity()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const amount = Number(customInputValue) * 100;
|
|
|
|
send(amount);
|
|
|
|
}
|
2024-08-06 10:48:06 +02:00
|
|
|
</script>
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
<div id="donation-box">
|
|
|
|
<div id="donation-types">
|
2024-08-06 20:26:53 +02:00
|
|
|
<button
|
|
|
|
id="donation-type-once"
|
|
|
|
class="donation-type"
|
|
|
|
on:click={() => processor = 'stripe'}
|
|
|
|
class:selected={processor === 'stripe'}
|
|
|
|
>
|
2024-08-06 12:45:25 +02:00
|
|
|
<div class="donation-type-icon"><IconCoin /></div>
|
|
|
|
<div class="donation-title">one-time donation</div>
|
|
|
|
<div class="donation-subtitle">processed by stripe</div>
|
|
|
|
</button>
|
2024-08-06 20:26:53 +02:00
|
|
|
<button
|
|
|
|
id="donation-type-monthly"
|
|
|
|
class="donation-type"
|
|
|
|
on:click={() => processor = 'liberapay'}
|
|
|
|
class:selected={processor === 'liberapay'}
|
|
|
|
>
|
2024-08-06 12:45:25 +02:00
|
|
|
<div class="donation-type-icon"><IconCalendarRepeat /></div>
|
|
|
|
<div class="donation-title">monthly donation</div>
|
|
|
|
<div class="donation-subtitle">processed by liberapay</div>
|
|
|
|
</button>
|
2024-08-06 10:48:06 +02:00
|
|
|
</div>
|
2024-08-06 12:45:25 +02:00
|
|
|
<div id="donation-options">
|
2024-08-06 20:26:53 +02:00
|
|
|
<DonationOption price={5} desc="cup of coffee" {send}>
|
2024-08-06 12:45:25 +02:00
|
|
|
<IconCup />
|
|
|
|
</DonationOption>
|
2024-08-06 20:26:53 +02:00
|
|
|
<DonationOption price={10} desc="full size pizza" {send}>
|
2024-08-06 12:45:25 +02:00
|
|
|
<IconPizza />
|
|
|
|
</DonationOption>
|
2024-08-06 20:26:53 +02:00
|
|
|
<DonationOption price={15} desc="full lunch" {send}>
|
2024-08-06 12:45:25 +02:00
|
|
|
<IconToolsKitchen2 />
|
|
|
|
</DonationOption>
|
2024-08-06 20:26:53 +02:00
|
|
|
<DonationOption price={30} desc="two lunches" {send}>
|
2024-08-06 12:45:25 +02:00
|
|
|
<IconToolsKitchen2 />
|
|
|
|
</DonationOption>
|
2024-08-06 10:48:06 +02:00
|
|
|
</div>
|
2024-08-06 12:45:25 +02:00
|
|
|
<div id="donation-custom">
|
2024-08-06 20:29:39 +02:00
|
|
|
<div
|
|
|
|
id="input-container"
|
|
|
|
class:focused={customFocused}
|
|
|
|
>
|
|
|
|
{#if customInputValue || customInput?.validity.badInput}
|
|
|
|
<span id="input-dollar-sign">
|
|
|
|
$
|
|
|
|
</span>
|
|
|
|
{/if}
|
|
|
|
<input
|
|
|
|
id="donation-custom-input"
|
|
|
|
type="number"
|
|
|
|
min="2"
|
|
|
|
max="10000"
|
|
|
|
step=".01"
|
|
|
|
required
|
|
|
|
placeholder="custom amount (from $2)"
|
|
|
|
bind:this={customInput}
|
|
|
|
bind:value={customInputValue}
|
|
|
|
on:input ={() => customFocused = true}
|
|
|
|
on:focus ={() => customFocused = true}
|
|
|
|
on:blur ={() => customFocused = false}
|
|
|
|
on:keydown={(e) => e.key === 'Enter' && sendCustom()}
|
|
|
|
/>
|
|
|
|
</div>
|
2024-08-06 12:45:25 +02:00
|
|
|
<button
|
|
|
|
id="donation-custom-submit"
|
2024-08-06 20:28:54 +02:00
|
|
|
on:click={sendCustom}
|
2024-08-06 12:45:25 +02:00
|
|
|
>
|
|
|
|
<IconArrowRight />
|
|
|
|
</button>
|
2024-08-06 10:48:06 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<style>
|
2024-08-06 12:45:25 +02:00
|
|
|
#donation-box {
|
|
|
|
--donation-box-padding: 12px;
|
2024-08-06 10:48:06 +02:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
2024-08-06 12:53:40 +02:00
|
|
|
width: calc(100% - var(--donate-border-radius) * 2);
|
2024-08-06 12:45:25 +02:00
|
|
|
max-width: 480px;
|
2024-08-06 10:48:06 +02:00
|
|
|
|
|
|
|
padding: var(--donate-border-radius);
|
|
|
|
border-radius: var(--donate-border-radius);
|
|
|
|
gap: calc(var(--donate-border-radius) / 2);
|
|
|
|
|
|
|
|
color: white;
|
|
|
|
background: linear-gradient(
|
|
|
|
180deg,
|
|
|
|
var(--donate-gradient-end) 0%,
|
|
|
|
var(--donate-gradient-start) 80%
|
|
|
|
);
|
2024-08-06 12:45:25 +02:00
|
|
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, var(--donate-border-opacity))
|
|
|
|
inset;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
#donation-types {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
gap: var(--donation-box-padding);
|
|
|
|
}
|
|
|
|
|
|
|
|
.donation-type,
|
|
|
|
:global(.donation-option) {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: flex-start;
|
|
|
|
text-align: left;
|
|
|
|
border-radius: var(--donation-box-padding);
|
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
|
padding: 14px 18px;
|
|
|
|
color: var(--white);
|
|
|
|
gap: 0;
|
|
|
|
letter-spacing: -0.3px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.donation-type {
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
:global(#donation-box button:not(:focus-visible)) {
|
|
|
|
box-shadow: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
@media (hover: hover) {
|
|
|
|
:global(#donation-box button:hover) {
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
}
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
.donation-type.selected {
|
|
|
|
background: rgba(255, 255, 255, 0.15);
|
|
|
|
}
|
|
|
|
|
|
|
|
.donation-type.selected:not(:focus-visible) {
|
|
|
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1) inset !important;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
.donation-subtitle {
|
2024-08-06 12:45:25 +02:00
|
|
|
line-height: 1.5;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
.donation-type-icon :global(svg) {
|
|
|
|
width: 28px;
|
|
|
|
height: 28px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
:global(.donation-title) {
|
2024-08-06 10:48:06 +02:00
|
|
|
display: flex;
|
2024-08-06 12:45:25 +02:00
|
|
|
align-items: center;
|
|
|
|
font-size: 16px;
|
|
|
|
gap: 4px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
:global(.donation-subtitle) {
|
|
|
|
font-size: 13px;
|
|
|
|
color: #9a9a9a;
|
|
|
|
}
|
|
|
|
|
|
|
|
#donation-options {
|
2024-08-06 10:48:06 +02:00
|
|
|
display: flex;
|
2024-08-06 12:45:25 +02:00
|
|
|
overflow-x: scroll;
|
|
|
|
gap: 6px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
#donation-custom {
|
2024-08-06 10:48:06 +02:00
|
|
|
display: flex;
|
2024-08-06 12:45:25 +02:00
|
|
|
gap: 6px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 20:29:39 +02:00
|
|
|
#input-container {
|
2024-08-06 12:45:25 +02:00
|
|
|
padding: 12px 18px;
|
|
|
|
width: 100%;
|
|
|
|
border-radius: 12px;
|
|
|
|
color: var(--white);
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
2024-08-06 20:29:39 +02:00
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 4px;
|
|
|
|
}
|
|
|
|
|
|
|
|
#input-dollar-sign {
|
|
|
|
animation: grow-in .05s linear;
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes grow-in {
|
|
|
|
from { font-size: 0 }
|
|
|
|
to { font-size: inherit }
|
|
|
|
}
|
|
|
|
|
|
|
|
#input-container, #donation-custom-input {
|
|
|
|
font-size: 13px;
|
|
|
|
}
|
|
|
|
|
|
|
|
#donation-custom-input {
|
|
|
|
flex: 1;
|
|
|
|
background-color: transparent;
|
|
|
|
color: var(--white);
|
2024-08-06 10:48:06 +02:00
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
|
2024-08-06 20:29:39 +02:00
|
|
|
#donation-custom-input:focus-visible {
|
|
|
|
box-shadow: unset !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
#input-container.focused {
|
|
|
|
box-shadow: 0 0 0 1.5px var(--blue) inset;
|
|
|
|
outline: var(--blue) 0.5px solid;
|
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
#donation-custom-submit {
|
|
|
|
color: var(--white);
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
aspect-ratio: 1/1;
|
|
|
|
padding: 8px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
|
|
|
|
2024-08-06 12:45:25 +02:00
|
|
|
#donation-custom-submit :global(svg) {
|
|
|
|
width: 24px;
|
|
|
|
height: 24px;
|
2024-08-06 10:48:06 +02:00
|
|
|
}
|
2024-08-06 12:46:32 +02:00
|
|
|
|
|
|
|
#donation-custom-input::-webkit-outer-spin-button,
|
|
|
|
#donation-custom-input::-webkit-inner-spin-button {
|
|
|
|
-webkit-appearance: none;
|
|
|
|
margin: 0;
|
|
|
|
}
|
2024-08-06 10:48:06 +02:00
|
|
|
</style>
|