-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: secrets integration & email list
- Loading branch information
Showing
15 changed files
with
424 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
watch_file "./nix/env.nix" "./nix/fmt.nix" "./nix/packages.nix" "./nix/shells.nix" "./nix/pre-commit.nix" "./flake.nix" "./parse.nix" | ||
use flake | ||
PATH_add node_modules/.bin | ||
pls setup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"workspaceId": "e3939ee2-8192-4d9d-b2dc-f342b4c8afd2", | ||
"defaultEnvironment": "lapras", | ||
"gitBranchToEnvironmentMapping": null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -eou pipefail | ||
|
||
echo "🔏 Setting up secrets for local development..." | ||
|
||
set +e | ||
(infisical secrets) &>/dev/null | ||
ec="$?" | ||
set -e | ||
|
||
if [ "$ec" != '0' ]; then | ||
infisical login | ||
fi | ||
|
||
echo "⬇️ Downloading local secrets..." | ||
infisical export --format dotenv >.env | ||
echo "✅ Secrets set up for local development!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { defineAction, ActionError } from 'astro:actions'; | ||
import { z } from 'astro:schema'; | ||
|
||
export const server = { | ||
submitWaitlistEmail: defineAction({ | ||
input: z.object({ | ||
email: z.string().email(), | ||
}), | ||
handler: async ({ email }) => { | ||
const options = { | ||
method: 'POST', | ||
headers: { | ||
accept: 'application/json', | ||
'content-type': 'application/json', | ||
'api-key': import.meta.env.BREVO_API_KEY, | ||
}, | ||
body: JSON.stringify({ | ||
email, | ||
listIds: [5], | ||
}), | ||
}; | ||
|
||
const r = await fetch('https://api.brevo.com/v3/contacts', options); | ||
|
||
if (!r.ok) { | ||
console.error(r); | ||
throw new ActionError({ | ||
message: 'Something went wrong while adding you to the waitlist 😢', | ||
statusCode: 500, | ||
}); | ||
} | ||
return email; | ||
}, | ||
}), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
--- | ||
import PulsatingButton from "../ui/pulsating-button"; | ||
import {Input} from "../ui/input"; | ||
--- | ||
|
||
<div class="flex flex-col gap-2"> | ||
<Input id="email" className="text-center text-md" type="email" placeholder="Enter your email"/> | ||
<p id="error" class="text-xs text-red-500 text-center py-0"></p> | ||
</div> | ||
<PulsatingButton id="reserve" className="text-md">Reserve your Spot</PulsatingButton> | ||
<script> | ||
import z from "zod"; | ||
import {actions} from 'astro:actions'; | ||
import {toast} from "sonner"; | ||
import confetti from "canvas-confetti"; | ||
|
||
const fireworksClick = () => { | ||
const duration = 2500; | ||
const animationEnd = Date.now() + duration; | ||
const defaults = {startVelocity: 30, spread: 360, ticks: 60, zIndex: 0}; | ||
|
||
const randomInRange = (min: number, max: number) => | ||
Math.random() * (max - min) + min; | ||
|
||
const interval = window.setInterval(() => { | ||
const timeLeft = animationEnd - Date.now(); | ||
|
||
if (timeLeft <= 0) { | ||
return clearInterval(interval); | ||
} | ||
|
||
const particleCount = 50 * (timeLeft / duration); | ||
confetti({ | ||
...defaults, | ||
particleCount, | ||
origin: {x: randomInRange(0.1, 0.3), y: Math.random() - 0.2}, | ||
}); | ||
confetti({ | ||
...defaults, | ||
particleCount, | ||
origin: {x: randomInRange(0.7, 0.9), y: Math.random() - 0.2}, | ||
}); | ||
}, 250); | ||
} | ||
|
||
const join = document.getElementById('reserve'); | ||
const emailInput = document.getElementById('email'); | ||
const errorBox = document.getElementById('error'); | ||
|
||
function emailValidator(email: string): string[] { | ||
|
||
const result = z.string().email('Invalid email').safeParse(email); | ||
if (result.success) return []; | ||
return result.error.errors.map(x => x.message); | ||
} | ||
|
||
emailInput?.addEventListener('input', async () => { | ||
const email = emailInput?.value ?? ""; | ||
const errors = emailValidator(email); | ||
if (errors.length > 0) { | ||
emailInput?.classList.add('border-red-500'); | ||
emailInput?.classList.add('focus:outline-red-500'); | ||
errorBox!.innerHTML = errors[0]; | ||
} else { | ||
emailInput?.classList.remove('border-red-500'); | ||
emailInput?.classList.remove('focus:outline-red-500'); | ||
errorBox!.innerHTML = ''; | ||
} | ||
}); | ||
|
||
join?.addEventListener('click', async () => { | ||
|
||
const email = emailInput?.value ?? ""; | ||
const errors = emailValidator(email) | ||
if (errors.length > 0) { | ||
toast.error(errors[0]); | ||
return; | ||
} | ||
const {error} = await actions.submitWaitlistEmail({email}); | ||
emailInput!.value = ''; | ||
if (!error) { | ||
fireworksClick(); | ||
toast.success('You have been added to the waitlist 🎉'); | ||
} else { | ||
errorBox!.innerHTML = error; | ||
toast.error('Something went wrong while adding you to the waitlist 😢'); | ||
} | ||
|
||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import * as React from 'react'; | ||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; | ||
|
||
import { cn } from '@/lib/utils'; | ||
import { buttonVariants } from '@/components/ui/button'; | ||
|
||
const AlertDialog = AlertDialogPrimitive.Root; | ||
|
||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger; | ||
|
||
const AlertDialogPortal = AlertDialogPrimitive.Portal; | ||
|
||
const AlertDialogOverlay = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Overlay | ||
className={cn( | ||
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', | ||
className, | ||
)} | ||
{...props} | ||
ref={ref} | ||
/> | ||
)); | ||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; | ||
|
||
const AlertDialogContent = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPortal> | ||
<AlertDialogOverlay /> | ||
<AlertDialogPrimitive.Content | ||
ref={ref} | ||
className={cn( | ||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
</AlertDialogPortal> | ||
)); | ||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; | ||
|
||
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( | ||
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} /> | ||
); | ||
AlertDialogHeader.displayName = 'AlertDialogHeader'; | ||
|
||
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( | ||
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} /> | ||
); | ||
AlertDialogFooter.displayName = 'AlertDialogFooter'; | ||
|
||
const AlertDialogTitle = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Title>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Title ref={ref} className={cn('text-lg font-semibold', className)} {...props} /> | ||
)); | ||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; | ||
|
||
const AlertDialogDescription = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Description>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} /> | ||
)); | ||
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; | ||
|
||
const AlertDialogAction = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Action>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} /> | ||
)); | ||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; | ||
|
||
const AlertDialogCancel = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Cancel | ||
ref={ref} | ||
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)} | ||
{...props} | ||
/> | ||
)); | ||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; | ||
|
||
export { | ||
AlertDialog, | ||
AlertDialogPortal, | ||
AlertDialogOverlay, | ||
AlertDialogTrigger, | ||
AlertDialogContent, | ||
AlertDialogHeader, | ||
AlertDialogFooter, | ||
AlertDialogTitle, | ||
AlertDialogDescription, | ||
AlertDialogAction, | ||
AlertDialogCancel, | ||
}; |
Oops, something went wrong.