diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx
index fa6598da..78ec7c2c 100644
--- a/apps/www/app/layout.tsx
+++ b/apps/www/app/layout.tsx
@@ -22,7 +22,7 @@ export const metadata = {
template: `%s | ${siteConfig.name}`,
},
description: siteConfig.description,
- keywords: ["Badget keywords"],
+ keywords: ["badget", "open source", "tracker", "free", "budget", "finance"],
authors: [
{
name: "christer",
diff --git a/apps/www/app/onboarding/page.tsx b/apps/www/app/onboarding/page.tsx
new file mode 100644
index 00000000..5bb4893d
--- /dev/null
+++ b/apps/www/app/onboarding/page.tsx
@@ -0,0 +1,48 @@
+"use client";
+
+import Link from "next/link";
+import { useRouter, useSearchParams } from "next/navigation";
+import { AnimatePresence, motion } from "framer-motion";
+import { ArrowLeft } from "lucide-react";
+
+import { Toaster } from "@/components/ui/toaster";
+import ConnectAccount from "@/components/onboarding/connect-account";
+import FinancialGoals from "@/components/onboarding/financial-goals";
+import Welcome from "@/components/onboarding/welcome";
+
+export default function Intro() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const step = searchParams.get("step");
+
+ return (
+
+
+
+ {step ? (
+ router.back()}
+ >
+
+
+ ) : (
+
+ )}
+ {step === "financial-goals" && }
+ {step === "connect-accounts" && }
+ {step === "done" && (
+
+
+ Done!
+
+
+ Go to Dashboard
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/www/components/onboarding/bank-uploader.tsx b/apps/www/components/onboarding/bank-uploader.tsx
new file mode 100644
index 00000000..084bcd72
--- /dev/null
+++ b/apps/www/components/onboarding/bank-uploader.tsx
@@ -0,0 +1,173 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+import useMediaQuery from "@/hooks/use-media-query"
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "@/components/ui/drawer"
+import { Input } from "@/components/ui/input"
+import { BanksChooser } from "./banks-chooser"
+import { toast } from "../ui/use-toast"
+import { useRouter, useSearchParams } from "next/navigation"
+import { useCallback } from "react"
+import { z } from "zod"
+import { useForm } from "react-hook-form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"
+import CSVParser from "./banks-data/parse-data/generic-csv-parser"
+
+export function BankUploader() {
+ const [open, setOpen] = React.useState(false)
+ const [uploadedBanks, setUploadedBanks] = React.useState([]);
+ const isDesktop = useMediaQuery().isDesktop
+
+ if (isDesktop) {
+ return (
+
+
+ Import File
+
+
+
+ Import File
+
+ Export transactions data as a file from bank's website.
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+ Import File
+
+
+ We are still working on mobile support. Badget is currently best experienced on desktop.
+ {/*
+ Import File
+
+ Export transactions data as a file from bank's website.
+
+
+
+
+
+ Cancel
+
+ */}
+
+
+ )
+}
+
+function UploadForm({ className, banks, setBanks }: { className: string, banks: string[], setBanks: React.Dispatch> }) {
+ const [selectedBank, setSelectedBank] = React.useState("");
+ const [file, setFile] = React.useState(null);
+ const [uploading, setUploading] = React.useState(false);
+
+ const handleBankSelect = (value: string) => {
+ setSelectedBank(value);
+ };
+
+ const handleDeleteBank = (index: number) => {
+ setBanks(prevBanks => prevBanks.filter((_, i) => i !== index));
+ };
+
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ const handleFileChange = (event) => {
+ const selectedFile = event.target.files[0];
+ setFile(selectedFile);
+ };
+
+
+ const handleUpload = () => {
+ if (!selectedBank) {
+ return;
+ }
+
+ if (selectedBank == "American Express Credit Card") {
+ AmexCredit({ file });
+ }
+
+ setBanks(prevBanks => [...prevBanks, selectedBank]);
+ toast({
+ title: "File uploaded",
+ description: "Your file has been uploaded successfully.",
+ })
+ };
+
+ return (
+
+ {/*
*/}
+
+ {/*
+
+
+
+ Upload
+
+
+
+ {banks.length > 0 && (
+
+
Selected Banks:
+
+ {banks.map((bank, index) => (
+
+ {bank}
+ handleDeleteBank(index)}>Delete
+
+ ))}
+
+
+
+ router.push(
+ "/onboarding" + "?" + createQueryString("step", "done"),
+ )
+ }
+ className="bg-green-500 hover:bg-green-400 h-10 w-32 mt-8"
+ >
+ Next
+
+
+
+ )} */}
+
+ )
+}
diff --git a/apps/www/components/onboarding/banks-chooser.tsx b/apps/www/components/onboarding/banks-chooser.tsx
new file mode 100644
index 00000000..44a46364
--- /dev/null
+++ b/apps/www/components/onboarding/banks-chooser.tsx
@@ -0,0 +1,79 @@
+"use client"
+
+import * as React from "react"
+import { Check, ChevronsUpDown, Landmark } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from "@/components/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+import Image from "next/image"
+import banks from "./banks-data/banks-list"
+
+export function BanksChooser({ onSelect }: { onSelect: (value: string) => void }) {
+ const [open, setOpen] = React.useState(false)
+ const [value, setValue] = React.useState("")
+
+ return (
+
+
+
+ {value
+ ? banks.find((bank) => bank.value === value)?.label
+ : "Select bank..."}
+
+
+
+
+
+
+
+ Bank not found
+ Please raise an issue on GitHub
+
+
+ {banks.map((bank) => (
+ {
+ setValue(currentValue === value ? "" : currentValue)
+ setOpen(false)
+ onSelect(bank.label);
+ }}
+ className="h-16 pr-6"
+ >
+
+
+ {/* TODO: Add images instead of just the icon */}
+
+
{bank.label}
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/www/components/onboarding/banks-data/banks-list.tsx b/apps/www/components/onboarding/banks-data/banks-list.tsx
new file mode 100644
index 00000000..9596ae3d
--- /dev/null
+++ b/apps/www/components/onboarding/banks-data/banks-list.tsx
@@ -0,0 +1,50 @@
+const banks = [
+ {
+ value: "americanexpresscreditcard",
+ label: "American Express Credit Card",
+ },
+ {
+ value: "bankofamericachecking",
+ label: "Bank of America Checking",
+ image: "boa.png",
+ },
+ {
+ value: "bankofamericacreditcard",
+ label: "Bank of America Credit Card",
+ image: "boa.png",
+ },
+ {
+ value: "bmochecking",
+ label: "BMO Checking",
+ },
+ {
+ value: "chasecreditcard",
+ label: "Chase Credit Card",
+ },
+ {
+ value: "citibankcreditcard",
+ label: "Citibank Credit Card",
+ },
+ {
+ value: "citizenschecking",
+ label: "Citizens Checking",
+ },
+ {
+ value: "flagstarchecking",
+ label: "Flagstar Checking",
+ },
+ {
+ value: "pncchecking",
+ label: "PNC Checking",
+ },
+ {
+ value: "usbank",
+ label: "US Bank",
+ },
+ {
+ value: "wellsfargocreditcard",
+ label: "Wells Fargo Credit Card",
+ },
+];
+
+export default banks;
diff --git a/apps/www/components/onboarding/banks-data/images/boa.png b/apps/www/components/onboarding/banks-data/images/boa.png
new file mode 100644
index 00000000..d49feb36
Binary files /dev/null and b/apps/www/components/onboarding/banks-data/images/boa.png differ
diff --git a/apps/www/components/onboarding/banks-data/images/testing.csv b/apps/www/components/onboarding/banks-data/images/testing.csv
new file mode 100644
index 00000000..bd21447f
--- /dev/null
+++ b/apps/www/components/onboarding/banks-data/images/testing.csv
@@ -0,0 +1,11 @@
+"Date","Name","Amount"
+"2016-01-01","Food","10.00"
+"2016-01-01","Transport","5.00"
+"2016-01-02","Food","20.00"
+"2016-01-02","Transport","10.00"
+"2016-01-03","Food","30.00"
+"2016-01-03","Transport","15.00"
+"2016-01-04","Food","40.00"
+"2016-01-04","Transport","20.00"
+"2016-01-05","Food","50.00"
+"2016-01-05","Transport","25.00"
diff --git a/apps/www/components/onboarding/banks-data/parse-data/amex-credit.tsx b/apps/www/components/onboarding/banks-data/parse-data/amex-credit.tsx
new file mode 100644
index 00000000..caea7954
--- /dev/null
+++ b/apps/www/components/onboarding/banks-data/parse-data/amex-credit.tsx
@@ -0,0 +1,5 @@
+// TODO: finish individual components later
+
+function AmexCredit({ file }: { file: File | null }) {
+ console.log(file);
+}
diff --git a/apps/www/components/onboarding/banks-data/parse-data/generic-csv-parser.tsx b/apps/www/components/onboarding/banks-data/parse-data/generic-csv-parser.tsx
new file mode 100644
index 00000000..cbda03c8
--- /dev/null
+++ b/apps/www/components/onboarding/banks-data/parse-data/generic-csv-parser.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import React, { useCallback } from "react";
+import ReactDOM from "react-dom";
+import { Importer, ImporterField } from "react-csv-importer";
+
+// theme CSS for React CSV Importer
+import "react-csv-importer/dist/index.css";
+import { useRouter, useSearchParams } from "next/navigation";
+
+export default function CSVParser() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ return (
+
+ {
+ // required, receives a list of parsed objects based on defined fields and user column mapping;
+ console.log("Rows processed:", rows);
+
+ // mock timeout to simulate processing
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ }}
+ chunkSize={10000} // optional, internal parsing chunk size in bytes
+ defaultNoHeader={false} // optional, keeps "data has headers" checkbox off by default
+ restartable={true} // optional, lets user choose to upload another file when import is complete
+ onStart={({ file, fields }) => {
+ // optional, invoked when user has mapped columns and started import
+ console.log("starting import of file", file, "with fields", fields);
+ }}
+ onComplete={({ file, fields }) => {
+ // optional, invoked right after import is done (but user did not dismiss/reset the widget yet)
+ console.log("finished import of file", file, "with fields", fields);
+
+ }}
+ onClose={() => {
+ // optional, invoked when import is done and user clicked "Finish"
+ console.log("importer dismissed");
+ router.push(
+ "/onboarding" + "?" + createQueryString("step", "done"),
+ )
+ }}
+ >
+
+
+
+
+
+ )
+}
diff --git a/apps/www/components/onboarding/beams.tsx b/apps/www/components/onboarding/beams.tsx
new file mode 100644
index 00000000..40c1157b
--- /dev/null
+++ b/apps/www/components/onboarding/beams.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import React from "react";
+import { motion } from "framer-motion";
+
+import { cn } from "@/lib/utils";
+
+export const BackgroundBeams = React.memo(
+ ({ className }: { className?: string }) => {
+ const paths = [
+ "M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
+ "M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
+ "M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
+ "M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
+ "M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
+ "M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
+ "M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
+ "M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
+ "M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
+ "M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
+ "M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
+ "M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
+ "M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
+ "M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
+ "M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
+ "M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
+ "M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
+ "M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
+ "M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
+ "M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
+ "M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
+ "M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
+ "M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
+ "M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
+ "M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
+ "M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
+ "M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
+ "M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
+ "M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
+ "M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
+ "M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
+ "M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
+ "M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
+ "M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
+ "M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
+ "M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
+ "M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
+ "M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
+ "M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
+ "M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
+ "M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
+ "M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
+ "M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
+ "M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
+ "M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
+ "M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
+ "M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
+ "M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
+ "M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
+ "M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
+ ];
+ return (
+
+
+
+
+ {paths.map((path, index) => (
+
+ ))}
+
+ {paths.map((path, index) => (
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+ },
+);
+
+BackgroundBeams.displayName = "BackgroundBeams";
diff --git a/apps/www/components/onboarding/connect-account.tsx b/apps/www/components/onboarding/connect-account.tsx
new file mode 100644
index 00000000..f03e68cb
--- /dev/null
+++ b/apps/www/components/onboarding/connect-account.tsx
@@ -0,0 +1,170 @@
+"use client";
+
+import React, { useCallback, useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { motion, useMotionTemplate, useMotionValue } from "framer-motion";
+
+import { cn } from "@/lib/utils";
+
+import { STAGGER_CHILD_VARIANTS } from "./onboarding-constants";
+import { BankUploader } from "./bank-uploader";
+import { Badge } from "../ui/badge";
+import { Button } from "../ui/button";
+
+export default function ConnectAccount() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ return (
+
+
+
+ Connect Accounts
+
+
+ Securely connect your bank accounts with Badget.
+ {/* Powered by{" "}
+ Plaid. */}
+
+
+
+ {/*
+
+ */}
+
+
+
+ );
+}
+
+export const SecureCard = ({
+ className,
+ text,
+}: {
+ className?: string;
+ text: string;
+}) => {
+ let mouseX = useMotionValue(0);
+ let mouseY = useMotionValue(0);
+
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ const [randomString, setRandomString] = useState("");
+
+ useEffect(() => {
+ let str = generateRandomString(1500);
+ setRandomString(str);
+ }, []);
+
+ function onMouseMove({ currentTarget, clientX, clientY }: any) {
+ let { left, top } = currentTarget.getBoundingClientRect();
+ mouseX.set(clientX - left);
+ mouseY.set(clientY - top);
+
+ const str = generateRandomString(1500);
+ setRandomString(str);
+ }
+
+ return (
+
+ );
+};
+
+export function CardPattern({ mouseX, mouseY, randomString }: any) {
+ let maskImage = useMotionTemplate`radial-gradient(250px at ${mouseX}px ${mouseY}px, white, transparent)`;
+ let style = { maskImage, WebkitMaskImage: maskImage };
+
+ return (
+
+
+
+
+
+ {randomString}
+
+
+
+ );
+}
+
+const characters =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+export const generateRandomString = (length: number) => {
+ let result = "";
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
+ }
+ return result;
+};
diff --git a/apps/www/components/onboarding/financial-goals.tsx b/apps/www/components/onboarding/financial-goals.tsx
new file mode 100644
index 00000000..74c1933e
--- /dev/null
+++ b/apps/www/components/onboarding/financial-goals.tsx
@@ -0,0 +1,97 @@
+import { useCallback } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { motion } from "framer-motion";
+import { AreaChart, BrainCircuit, PiggyBank } from "lucide-react";
+
+import { STAGGER_CHILD_VARIANTS } from "./onboarding-constants";
+
+export default function FinancialGoals() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ return (
+
+
+
+ Badget
+
+
+ What brings you here?
+
+
+
+
+ router.push(
+ "/onboarding" +
+ "?" +
+ createQueryString("step", "connect-accounts"),
+ )
+ }
+ >
+
+ Save more money
+
+
+ router.push(
+ "/onboarding" +
+ "?" +
+ createQueryString("step", "connect-accounts"),
+ )
+ }
+ >
+
+ Track my expenses
+
+
+ router.push(
+ "/onboarding" +
+ "?" +
+ createQueryString("step", "connect-accounts"),
+ )
+ }
+ >
+
+ Use AI Intelligence
+
+
+
+ );
+}
diff --git a/apps/www/components/onboarding/onboarding-constants.tsx b/apps/www/components/onboarding/onboarding-constants.tsx
new file mode 100644
index 00000000..7dc30f29
--- /dev/null
+++ b/apps/www/components/onboarding/onboarding-constants.tsx
@@ -0,0 +1,4 @@
+export const STAGGER_CHILD_VARIANTS = {
+ hidden: { opacity: 0, y: 20 },
+ show: { opacity: 1, y: 0, transition: { duration: 0.4, type: "spring" } },
+};
diff --git a/apps/www/components/onboarding/welcome.tsx b/apps/www/components/onboarding/welcome.tsx
new file mode 100644
index 00000000..bfecf108
--- /dev/null
+++ b/apps/www/components/onboarding/welcome.tsx
@@ -0,0 +1,72 @@
+import { useCallback } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { motion } from "framer-motion";
+
+import { Button } from "../ui/button";
+import { BackgroundBeams } from "./beams";
+import { STAGGER_CHILD_VARIANTS } from "./onboarding-constants";
+
+export default function Welcome() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const createQueryString = useCallback(
+ (name: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString());
+ params.set(name, value);
+
+ return params.toString();
+ },
+ [searchParams],
+ );
+
+ return (
+
+
+
+ Welcome to Badget
+
+
+ Badget gives you the power to securely track your finances using AI,
+ enabling smarter decisions.
+
+
+
+ router.push(
+ "/onboarding" +
+ "?" +
+ createQueryString("step", "financial-goals"),
+ )
+ }
+ >
+ Get Started
+
+
+
+
+
+ );
+}
diff --git a/apps/www/package.json b/apps/www/package.json
index 7e528a35..d7d9cc34 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -94,6 +94,7 @@
"postmark": "^3.1.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
+ "react-csv-importer": "^0.8.1",
"react-day-picker": "^8.9.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..c94af2ff
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,924 @@
+{
+ "name": "badget",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "badget",
+ "version": "0.1.0",
+ "license": "AGPL-3.0",
+ "dependencies": {
+ "framer-motion": "^11.0.8"
+ },
+ "devDependencies": {
+ "@projectx/prettier-config": "^0.1.0",
+ "prettier": "^3.2.5",
+ "turbo": "^1.12.4",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": ">=v20.10.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.23.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/chalk": {
+ "version": "2.4.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-convert": {
+ "version": "1.9.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-name": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/has-flag": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/supports-color": {
+ "version": "5.5.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.23.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-module-transforms": "^7.23.3",
+ "@babel/helpers": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@babel/template": "^7.23.9",
+ "@babel/traverse": "^7.23.9",
+ "@babel/types": "^7.23.9",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/json5": {
+ "version": "2.2.3",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.23.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.23.6",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.23.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "browserslist": "^4.22.2",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.22.20",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.23.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.22.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.22.15",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.22.15"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.23.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/helper-validator-identifier": "^7.22.20"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.22.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.22.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.23.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.23.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.23.9",
+ "@babel/traverse": "^7.23.9",
+ "@babel/types": "^7.23.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.23.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/parser": "^7.23.9",
+ "@babel/types": "^7.23.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/generator": "^7.23.6",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.23.9",
+ "@babel/types": "^7.23.9",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.23.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.23.4",
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.7.4",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@ianvs/prettier-plugin-sort-imports": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/core": "^7.21.8",
+ "@babel/generator": "^7.21.5",
+ "@babel/parser": "^7.21.8",
+ "@babel/traverse": "^7.21.5",
+ "@babel/types": "^7.21.5",
+ "semver": "^7.5.2"
+ },
+ "peerDependencies": {
+ "@vue/compiler-sfc": ">=3.0.0",
+ "prettier": "2 || 3"
+ },
+ "peerDependenciesMeta": {
+ "@vue/compiler-sfc": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.22",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@projectx/prettier-config": {
+ "resolved": "tooling/prettier",
+ "link": true
+ },
+ "node_modules/browserslist": {
+ "version": "4.22.2",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001565",
+ "electron-to-chromium": "^1.4.601",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001580",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.645",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "11.0.8",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.8.tgz",
+ "integrity": "sha512-1KSGNuqe1qZkS/SWQlDnqK2VCVzRVEoval379j0FiUBJAZoqgwyvqFkfvJbgW2IPFo4wX16K+M0k5jO23lCIjA==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "optionalDependencies": {
+ "@emotion/is-prop-valid": "^0.8.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.14",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.5.11",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ },
+ "prettier-plugin-twig-melody": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.4",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "license": "0BSD"
+ },
+ "node_modules/turbo": {
+ "version": "1.12.4",
+ "dev": true,
+ "license": "MPL-2.0",
+ "bin": {
+ "turbo": "bin/turbo"
+ },
+ "optionalDependencies": {
+ "turbo-darwin-64": "1.12.4",
+ "turbo-darwin-arm64": "1.12.4",
+ "turbo-linux-64": "1.12.4",
+ "turbo-linux-arm64": "1.12.4",
+ "turbo-windows-64": "1.12.4",
+ "turbo-windows-arm64": "1.12.4"
+ }
+ },
+ "node_modules/turbo-darwin-arm64": {
+ "version": "1.12.4",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/typescript": {
+ "version": "5.3.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.13",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "tooling/prettier": {
+ "name": "@projectx/prettier-config",
+ "version": "0.1.0",
+ "dev": true,
+ "dependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
+ "prettier": "^3.2.5",
+ "prettier-plugin-tailwindcss": "^0.5.11"
+ },
+ "devDependencies": {
+ "@projectx/tsconfig": "^0.1.0",
+ "typescript": "^5.3.3"
+ }
+ },
+ "tooling/prettier/node_modules/@projectx/tsconfig": {
+ "resolved": "tooling/typescript",
+ "link": true
+ },
+ "tooling/typescript": {
+ "name": "@projectx/tsconfig",
+ "version": "0.1.0",
+ "dev": true
+ }
+ }
+}
diff --git a/package.json b/package.json
index cae1907f..99a8b1ff 100644
--- a/package.json
+++ b/package.json
@@ -35,5 +35,8 @@
"turbo": "^1.12.4",
"typescript": "^5.3.3"
},
- "prettier": "@projectx/prettier-config"
-}
\ No newline at end of file
+ "prettier": "@projectx/prettier-config",
+ "dependencies": {
+ "framer-motion": "^11.0.8"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dcc11913..370d8a11 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,6 +7,10 @@ settings:
importers:
.:
+ dependencies:
+ framer-motion:
+ specifier: ^11.0.8
+ version: 11.0.8(react-dom@18.2.0)(react@18.2.0)
devDependencies:
'@projectx/prettier-config':
specifier: ^0.1.0
@@ -248,6 +252,9 @@ importers:
react:
specifier: ^18.2.0
version: 18.2.0
+ react-csv-importer:
+ specifier: ^0.8.1
+ version: 0.8.1(react-dom@18.2.0)(react@18.2.0)
react-day-picker:
specifier: ^8.9.1
version: 8.10.0(date-fns@2.30.0)(react@18.2.0)
@@ -6352,6 +6359,19 @@ packages:
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+ /@use-gesture/core@10.3.1:
+ resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
+ dev: false
+
+ /@use-gesture/react@10.3.1(react@18.2.0):
+ resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
+ peerDependencies:
+ react: '>= 16.8.0'
+ dependencies:
+ '@use-gesture/core': 10.3.1
+ react: 18.2.0
+ dev: false
+
/@vercel/analytics@1.1.2:
resolution: {integrity: sha512-CodhkLCQ/EHzjX8k+Qg+OzTBY0UadykrcfolfSOJVZZY/ZJM5nbhztm9KdbYvMfqKlasAr1+OYy0ThZnDA/MYA==}
dependencies:
@@ -8668,6 +8688,13 @@ packages:
dependencies:
flat-cache: 3.2.0
+ /file-selector@0.5.0:
+ resolution: {integrity: sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA==}
+ engines: {node: '>= 10'}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
/file-selector@0.6.0:
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
engines: {node: '>= 12'}
@@ -11319,6 +11346,10 @@ packages:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
dev: false
+ /papaparse@5.4.1:
+ resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==}
+ dev: false
+
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -11737,6 +11768,19 @@ packages:
strip-json-comments: 2.0.1
dev: false
+ /react-csv-importer@0.8.1(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-GcFQyeNJFFymqqnPi2YEr+UJWrLy0bdKEZ86n7HMcJ1O3ZGU0KQxB5CnYiKLT40QkWP8eAajXXy7o90lHmSj2Q==}
+ peerDependencies:
+ react: ^16.8.0 || >=17.0.0
+ react-dom: ^16.8.0 || >=17.0.0
+ dependencies:
+ '@use-gesture/react': 10.3.1(react@18.2.0)
+ papaparse: 5.4.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-dropzone: 12.1.0(react@18.2.0)
+ dev: false
+
/react-day-picker@8.10.0(date-fns@2.30.0)(react@18.2.0):
resolution: {integrity: sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==}
peerDependencies:
@@ -11757,6 +11801,18 @@ packages:
scheduler: 0.23.0
dev: false
+ /react-dropzone@12.1.0(react@18.2.0):
+ resolution: {integrity: sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog==}
+ engines: {node: '>= 10.13'}
+ peerDependencies:
+ react: '>= 16.8'
+ dependencies:
+ attr-accept: 2.2.2
+ file-selector: 0.5.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
/react-dropzone@14.2.3(react@18.2.0):
resolution: {integrity: sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==}
engines: {node: '>= 10.13'}
diff --git a/tooling/tailwind/index.ts b/tooling/tailwind/index.ts
index 523b4f03..2920ea25 100644
--- a/tooling/tailwind/index.ts
+++ b/tooling/tailwind/index.ts
@@ -1,6 +1,10 @@
import type { Config } from "tailwindcss";
import { fontFamily } from "tailwindcss/defaultTheme";
+const {
+ default: flattenColorPalette,
+} = require("tailwindcss/lib/util/flattenColorPalette");
+
const config = {
darkMode: ["class"],
content: [
@@ -137,7 +141,18 @@ const config = {
},
},
},
- plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
+ plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography"), addVariablesForColors],
} satisfies Config;
+function addVariablesForColors({ addBase, theme }: any) {
+ let allColors = flattenColorPalette(theme("colors"));
+ let newVars = Object.fromEntries(
+ Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
+ );
+
+ addBase({
+ ":root": newVars,
+ });
+}
+
export default config;