Skip to content

Commit

Permalink
feat: secrets integration & email list
Browse files Browse the repository at this point in the history
  • Loading branch information
kirinnee committed Dec 28, 2024
1 parent cdfe346 commit 6addbdc
Show file tree
Hide file tree
Showing 15 changed files with 424 additions and 3 deletions.
1 change: 1 addition & 0 deletions .envrc
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
5 changes: 5 additions & 0 deletions .infisical.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspaceId": "e3939ee2-8192-4d9d-b2dc-f342b4c8afd2",
"defaultEnvironment": "lapras",
"gitBranchToEnvironmentMapping": null
}
1 change: 1 addition & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ tasks:
desc: Setup the project for development
cmds:
- bun install
- ./scripts/local/secrets.sh
build:
desc: Build the project
cmds:
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion nix/packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ let
inherit
mirrord
swagger_typescript_api
infisical
sg
pls;
}
Expand All @@ -18,6 +17,7 @@ let
{
helm = kubernetes-helm;
inherit
infisical
coreutils
yq-go
gnused
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@
"dependencies": {
"@astrojs/react": "^4.1.2",
"@astrojs/tailwind": "^5.1.4",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-slot": "^1.1.1",
"@tabler/icons-react": "^3.26.0",
"@types/canvas-confetti": "^1.9.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"astro": "^5.1.1",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^11.15.0",
"lucide-react": "^0.469.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sonner": "^1.7.1",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
Expand Down
18 changes: 18 additions & 0 deletions scripts/local/secrets.sh
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!"
35 changes: 35 additions & 0 deletions src/actions/index.ts
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;
},
}),
};
4 changes: 2 additions & 2 deletions src/components/index/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import mascot from "../../assets/mascot.png";
import PulsatingButton from "@/components/ui/pulsating-button";
import {Button} from "../ui/button"
import {Input} from "../ui/input"
import Waitlist from "./Waitlist.astro";
---

<div id="container">
Expand All @@ -28,8 +29,7 @@ import {Input} from "../ui/input"
feel the fun, make the move
</div>
<div class="w-full flex flex-col text-sm justify-center gap-4 center">
<Input className="text-center text-md" type="email" placeholder="Email"/>
<PulsatingButton className="text-md">Reserve your Spot</PulsatingButton>
<Waitlist/>
</div>
</div>
</div>
Expand Down
90 changes: 90 additions & 0 deletions src/components/index/Waitlist.astro
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>
104 changes: 104 additions & 0 deletions src/components/ui/alert-dialog.tsx
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,
};
Loading

0 comments on commit 6addbdc

Please sign in to comment.