Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/csv import #232

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/www/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
48 changes: 48 additions & 0 deletions apps/www/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="dark mx-auto flex h-screen w-screen flex-col items-center justify-center overflow-x-hidden bg-zinc-950 text-white">
<Toaster />
<AnimatePresence mode="wait">
{step ? (
<button
className="group absolute left-2 top-10 z-40 rounded-full p-2 transition-all hover:bg-zinc-400 sm:left-10"
onClick={() => router.back()}
>
<ArrowLeft className="h-8 w-8 text-zinc-500 group-hover:text-zinc-800 group-active:scale-90" />
</button>
) : (
<Welcome />
)}
{step === "financial-goals" && <FinancialGoals />}
{step === "connect-accounts" && <ConnectAccount />}
{step === "done" && (
<div>
<h1 className="font-display max-w-md text-3xl font-semibold transition-colors sm:text-4xl">
Done!
</h1>
<Link href="/dashboard" className="rounded-2xl">
Go to Dashboard
</Link>
</div>
)}
</AnimatePresence>
</div>
);
}
173 changes: 173 additions & 0 deletions apps/www/components/onboarding/bank-uploader.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);
const isDesktop = useMediaQuery().isDesktop

if (isDesktop) {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Import File</Button>
</DialogTrigger>
<DialogContent className="h-screen max-w-screen-2xl">
<DialogHeader>
<DialogTitle>Import File</DialogTitle>
<DialogDescription>
Export transactions data as a file from bank's website.
</DialogDescription>
</DialogHeader>
<UploadForm banks={uploadedBanks} setBanks={setUploadedBanks} />
</DialogContent>
</Dialog>
)
}

return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
<Button>Import File</Button>
</DrawerTrigger>
<DrawerContent className="px-4 pb-4">
We are still working on mobile support. Badget is currently best experienced on desktop.
{/* <DrawerHeader className="text-left">
<DrawerTitle>Import File</DrawerTitle>
<DrawerDescription>
Export transactions data as a file from bank's website.
</DrawerDescription>
</DrawerHeader>
<UploadForm banks={uploadedBanks} setBanks={setUploadedBanks} className="px-4" />
<DrawerFooter className="pt-2">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter> */}
</DrawerContent>
</Drawer>
)
}

function UploadForm({ className, banks, setBanks }: { className: string, banks: string[], setBanks: React.Dispatch<React.SetStateAction<string[]>> }) {
const [selectedBank, setSelectedBank] = React.useState<string>("");
const [file, setFile] = React.useState<File | null>(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 (
<div className={cn("items-center py-4", className)}>
{/* <BanksChooser onSelect={handleBankSelect} /> */}
<CSVParser />
{/* <div className="mt-4 flex justify-center">
<Input type="file" placeholder="File" className="mb-2" onChange={handleFileChange} />

<Button
type="submit"
onClick={handleUpload}
>
Upload
</Button>
</div>

{banks.length > 0 && (
<div className="mt-4">
<h2 className="mb-2 font-bold">Selected Banks:</h2>
<ul className="space-y-2">
{banks.map((bank, index) => (
<li key={bank} className="flex justify-between items-center">
<span>{bank}</span>
<Button variant="destructive" onClick={() => handleDeleteBank(index)}>Delete</Button>
</li>
))}
</ul>
<div className="flex justify-center mt-4">
<Button
onClick={() =>
router.push(
"/onboarding" + "?" + createQueryString("step", "done"),
)
}
className="bg-green-500 hover:bg-green-400 h-10 w-32 mt-8"
>
Next
</Button>
</div>
</div>
)} */}
</div>
)
}
79 changes: 79 additions & 0 deletions apps/www/components/onboarding/banks-chooser.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="secondary"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value
? banks.find((bank) => bank.value === value)?.label
: "Select bank..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Search for bank..." />
<CommandEmpty>
Bank not found <br />
Please raise an issue on GitHub
</CommandEmpty>
<CommandGroup>
{banks.map((bank) => (
<CommandItem
key={bank.value}
value={bank.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
onSelect(bank.label);
}}
className="h-16 pr-6"
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === bank.value ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex items-center">
{/* TODO: Add images instead of just the icon */}
<Landmark />
<div className="ml-6">{bank.label}</div>
</div>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
)
}
50 changes: 50 additions & 0 deletions apps/www/components/onboarding/banks-data/banks-list.tsx
Original file line number Diff line number Diff line change
@@ -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;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions apps/www/components/onboarding/banks-data/images/testing.csv
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// TODO: finish individual components later

function AmexCredit({ file }: { file: File | null }) {
console.log(file);
}
Loading