diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index e0f07ed..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: CI
-
-on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
- workflow_dispatch:
-
-concurrency:
- group: ci-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- build:
- name: Typecheck and build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: pnpm/action-setup@v4
- with:
- version: 11
-
- - uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- # Fails if pnpm-lock.yaml drifts from package.json, so dependency
- # overrides cannot silently stop applying.
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- # next.config.mjs skips type errors during builds, so check types here
- # where a regression actually fails the run.
- - name: Typecheck
- run: pnpm exec tsc --noEmit
-
- - name: Build
- run: pnpm run build
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index f650315..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,27 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# env files
-.env*
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index 1bcf7b5..0000000
--- a/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# exchange-rate-export
-
-*Automatically synced with your [v0.dev](https://v0.dev) deployments*
-
-[](https://vercel.com/miti99/v0-exchange-rate-export)
-[](https://v0.dev/chat/projects/L8YX00K6aIO)
-
-## Overview
-
-This repository will stay in sync with your deployed chats on [v0.dev](https://v0.dev).
-Any changes you make to your deployed app will be automatically pushed to this repository from [v0.dev](https://v0.dev).
-
-## Deployment
-
-Your project is live at:
-
-**[https://vercel.com/miti99/v0-exchange-rate-export](https://vercel.com/miti99/v0-exchange-rate-export)**
-
-## Build your app
-
-Continue building your app on:
-
-**[https://v0.dev/chat/projects/L8YX00K6aIO](https://v0.dev/chat/projects/L8YX00K6aIO)**
-
-## How It Works
-
-1. Create and modify your project using [v0.dev](https://v0.dev)
-2. Deploy your chats from the v0 interface
-3. Changes are automatically pushed to this repository
-4. Vercel deploys the latest version from this repository
\ No newline at end of file
diff --git a/app/api/exchange/bidv/route.ts b/app/api/exchange/bidv/route.ts
deleted file mode 100644
index ea9094b..0000000
--- a/app/api/exchange/bidv/route.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { type NextRequest, NextResponse } from "next/server"
-
-async function safeFetch(bidvUrl: string, tries = 2) {
- const headers = {
- "User-Agent":
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125 Safari/537.36",
- Accept: "application/json, text/javascript, */*; q=0.01",
- Referer: "https://bidv.com.vn/",
- "X-Requested-With": "XMLHttpRequest",
- } as const
-
- // 1️⃣ helper that really performs the fetch
- const doFetch = (target: string) =>
- fetch(target, {
- headers,
- cache: "no-store",
- })
-
- // 2️⃣ Build the 2 proxy urls
- const encoded = encodeURIComponent(bidvUrl)
- const urls = [
- // Fast proxy, no wrapping
- `https://corsproxy.io/?${encoded}`,
- // More reliable fallback
- `https://api.allorigins.win/raw?url=${encoded}`,
- ]
-
- for (let attempt = 0; attempt < tries; attempt++) {
- for (const url of urls) {
- try {
- const res = await doFetch(url)
- if (res.ok) return res
- } catch {
- /* swallow & try next proxy */
- }
- }
- // small back-off
- await new Promise((r) => setTimeout(r, 400))
- }
-
- throw new Error("fetch failed")
-}
-
-export async function GET(request: NextRequest) {
- const { searchParams } = new URL(request.url)
- const date = searchParams.get("date")
-
- if (!date) {
- return NextResponse.json({ error: "Missing ?date=YYYY-MM-DD" }, { status: 400 })
- }
-
- // Convert YYYY-MM-DD to DD/MM/YYYY for BIDV API
- const [year, month, day] = date.split("-")
- const bidvDate = `${day}/${month}/${year}`
-
- try {
- // Step 1: Get time records for the date
- const timeUrl = `https://bidv.com.vn/ServicesBIDV/ExchangeDetailSearchTimeServlet?date=${bidvDate}`
- const timeRes = await safeFetch(timeUrl)
-
- if (!timeRes.ok) {
- return NextResponse.json({ error: `Time search failed: ${timeRes.status}` }, { status: timeRes.status })
- }
-
- const timeData = await timeRes.json()
-
- if (!timeData.data || timeData.data.length === 0) {
- return NextResponse.json({ error: "No time records found" }, { status: 404 })
- }
-
- // Step 2: Find the record with highest time value
- const latestRecord = timeData.data.reduce((prev: any, current: any) => (current.time > prev.time ? current : prev))
-
- // Step 3: Get exchange rates using the namerecord
- const rateUrl = `https://bidv.com.vn/ServicesBIDV/ExchangeDetailServlet?date=${bidvDate}&time=${latestRecord.namerecord}`
- const rateRes = await safeFetch(rateUrl)
-
- if (!rateRes.ok) {
- return NextResponse.json({ error: `Rate fetch failed: ${rateRes.status}` }, { status: rateRes.status })
- }
-
- const rateData = await rateRes.json()
- return NextResponse.json(rateData)
- } catch (error) {
- console.error("BIDV API error:", (error as Error).message)
- return NextResponse.json({ error: "Failed to fetch from BIDV" }, { status: 502 })
- }
-}
diff --git a/app/api/exchange/route.ts b/app/api/exchange/route.ts
deleted file mode 100644
index 0d7bf05..0000000
--- a/app/api/exchange/route.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { type NextRequest, NextResponse } from "next/server"
-
-export async function GET(request: NextRequest) {
- const { searchParams } = new URL(request.url)
- const date = searchParams.get("date")
-
- if (!date) {
- return NextResponse.json({ error: "Missing ?date=YYYY-MM-DD" }, { status: 400 })
- }
-
- const remoteURL = `https://techcombank.com/content/techcombank/web/vn/vi/cong-cu-tien-ich/ty-gia/_jcr_content.exchange-rates.${date}.integration.json`
-
- try {
- const res = await fetch(remoteURL, { cache: "no-store" })
- if (!res.ok) {
- return NextResponse.json({ error: `Upstream error ${res.status}` }, { status: res.status })
- }
- const json = await res.json()
- return NextResponse.json(json)
- } catch (error) {
- return NextResponse.json({ error: "Failed to fetch upstream" }, { status: 502 })
- }
-}
diff --git a/app/api/exchange/techcombank/route.ts b/app/api/exchange/techcombank/route.ts
deleted file mode 100644
index 0d7bf05..0000000
--- a/app/api/exchange/techcombank/route.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { type NextRequest, NextResponse } from "next/server"
-
-export async function GET(request: NextRequest) {
- const { searchParams } = new URL(request.url)
- const date = searchParams.get("date")
-
- if (!date) {
- return NextResponse.json({ error: "Missing ?date=YYYY-MM-DD" }, { status: 400 })
- }
-
- const remoteURL = `https://techcombank.com/content/techcombank/web/vn/vi/cong-cu-tien-ich/ty-gia/_jcr_content.exchange-rates.${date}.integration.json`
-
- try {
- const res = await fetch(remoteURL, { cache: "no-store" })
- if (!res.ok) {
- return NextResponse.json({ error: `Upstream error ${res.status}` }, { status: res.status })
- }
- const json = await res.json()
- return NextResponse.json(json)
- } catch (error) {
- return NextResponse.json({ error: "Failed to fetch upstream" }, { status: 502 })
- }
-}
diff --git a/app/globals.css b/app/globals.css
deleted file mode 100644
index ac68442..0000000
--- a/app/globals.css
+++ /dev/null
@@ -1,94 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-body {
- font-family: Arial, Helvetica, sans-serif;
-}
-
-@layer utilities {
- .text-balance {
- text-wrap: balance;
- }
-}
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 0 0% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary: 0 0% 9%;
- --primary-foreground: 0 0% 98%;
- --secondary: 0 0% 96.1%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 89.8%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem;
- --sidebar-background: 0 0% 98%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
- }
- .dark {
- --background: 0 0% 3.9%;
- --foreground: 0 0% 98%;
- --card: 0 0% 3.9%;
- --card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
- --popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
- --primary-foreground: 0 0% 9%;
- --secondary: 0 0% 14.9%;
- --secondary-foreground: 0 0% 98%;
- --muted: 0 0% 14.9%;
- --muted-foreground: 0 0% 63.9%;
- --accent: 0 0% 14.9%;
- --accent-foreground: 0 0% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 14.9%;
- --input: 0 0% 14.9%;
- --ring: 0 0% 83.1%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- --sidebar-background: 240 5.9% 10%;
- --sidebar-foreground: 240 4.8% 95.9%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
- --sidebar-accent: 240 3.7% 15.9%;
- --sidebar-accent-foreground: 240 4.8% 95.9%;
- --sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
diff --git a/app/layout.tsx b/app/layout.tsx
deleted file mode 100644
index df7c35d..0000000
--- a/app/layout.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import type React from "react"
-import type { Metadata } from "next"
-import "./globals.css"
-import { Analytics } from "@vercel/analytics/next"
-import { Suspense } from "react"
-
-export const metadata: Metadata = {
- title: "v0 App",
- description: "Created with v0",
- generator: "v0.dev",
-}
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode
-}>) {
- return (
-
-
-
- {children}
-
-
-
-
- )
-}
diff --git a/app/page.tsx b/app/page.tsx
deleted file mode 100644
index ca9d784..0000000
--- a/app/page.tsx
+++ /dev/null
@@ -1,419 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
-import { Download, Loader2 } from "lucide-react"
-import writeXlsxFile, { type Column } from "write-excel-file/browser"
-
-interface ExchangeRateData {
- date: string
- bank: string
- currency: string
- askRate: string
- bidRateCK: string
- bidRateTM: string
- askRateTM: string
- inputDate: string
-}
-
-// Mirrors the column order of the CSV export. Every rate arrives from the API
-// as a string and is written as-is, so no cell is coerced to a number.
-const XLSX_COLUMNS: Column[] = [
- { header: "Date", cell: (row) => row.date },
- { header: "Bank", cell: (row) => row.bank },
- { header: "Currency", cell: (row) => row.currency },
- { header: "Ask Rate", cell: (row) => row.askRate },
- { header: "Bid Rate CK", cell: (row) => row.bidRateCK },
- { header: "Bid Rate TM", cell: (row) => row.bidRateTM },
- { header: "Ask Rate TM", cell: (row) => row.askRateTM },
- { header: "Input Date", cell: (row) => row.inputDate },
-]
-
-type BankType = "techcombank" | "bidv"
-
-// Currency options for each bank
-const BANK_CURRENCIES = {
- techcombank: [
- { value: "USD (50,100)", label: "USD (50,100)" },
- { value: "USD (1,2)", label: "USD (1,2)" },
- { value: "USD (5,10,20)", label: "USD (5,10,20)" },
- { value: "EUR", label: "EUR" },
- { value: "GBP", label: "GBP" },
- { value: "JPY", label: "JPY" },
- { value: "AUD", label: "AUD" },
- { value: "CAD", label: "CAD" },
- { value: "CHF", label: "CHF" },
- { value: "CNY", label: "CNY" },
- { value: "HKD", label: "HKD" },
- { value: "SGD", label: "SGD" },
- { value: "THB", label: "THB" },
- { value: "KRW", label: "KRW" },
- { value: "NZD", label: "NZD" },
- ],
- bidv: [
- { value: "USD", label: "USD" },
- { value: "USD(1-2-5)", label: "USD (1-2-5)" },
- { value: "USD(10-20)", label: "USD (10-20)" },
- { value: "EUR", label: "EUR" },
- { value: "GBP", label: "GBP" },
- { value: "JPY", label: "JPY" },
- { value: "AUD", label: "AUD" },
- { value: "CAD", label: "CAD" },
- { value: "CHF", label: "CHF" },
- { value: "CNY", label: "CNY" },
- { value: "HKD", label: "HKD" },
- { value: "SGD", label: "SGD" },
- { value: "THB", label: "THB" },
- { value: "KRW", label: "KRW" },
- { value: "NZD", label: "NZD" },
- { value: "SEK", label: "SEK" },
- { value: "DKK", label: "DKK" },
- { value: "NOK", label: "NOK" },
- { value: "RUB", label: "RUB" },
- { value: "TWD", label: "TWD" },
- { value: "MYR", label: "MYR" },
- { value: "SAR", label: "SAR" },
- { value: "KWD", label: "KWD" },
- { value: "LAK", label: "LAK" },
- { value: "XAU", label: "Gold (XAU)" },
- ],
-}
-
-// Helper function to get default dates
-const getDefaultDates = () => {
- const today = new Date()
- const sevenDaysAgo = new Date()
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
-
- return {
- endDate: today.toISOString().split("T")[0],
- startDate: sevenDaysAgo.toISOString().split("T")[0],
- }
-}
-
-export default function ExchangeRateExporter() {
- const [bank, setBank] = useState("techcombank")
- const [currency, setCurrency] = useState("USD (50,100)")
- const defaultDates = getDefaultDates()
- const [startDate, setStartDate] = useState(defaultDates.startDate)
- const [endDate, setEndDate] = useState(defaultDates.endDate)
- const [data, setData] = useState([])
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState("")
-
- // Update currency when bank changes
- const handleBankChange = (newBank: BankType) => {
- setBank(newBank)
- // Set default currency for the selected bank
- const defaultCurrency = newBank === "techcombank" ? "USD (50,100)" : "USD"
- setCurrency(defaultCurrency)
- }
-
- const generateDateRange = (start: string, end: string): string[] => {
- const dates = []
- const startDate = new Date(start)
- const endDate = new Date(end)
-
- for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
- dates.push(new Date(d).toISOString().split("T")[0])
- }
-
- return dates
- }
-
- const fetchTechcombankRate = async (date: string): Promise => {
- try {
- const res = await fetch(`/api/exchange/techcombank?date=${date}`)
- if (!res.ok) {
- if (res.status === 404) {
- return null
- }
- console.error(`Techcombank API returned ${res.status} for ${date}`)
- return null
- }
-
- const result = await res.json()
- const currencyData = result.exchangeRate?.data?.find((item: any) => item.label === currency)
- if (!currencyData) return null
-
- return {
- date,
- bank: "Techcombank",
- currency,
- askRate: currencyData.askRate || "N/A",
- bidRateCK: currencyData.bidRateCK || "N/A",
- bidRateTM: currencyData.bidRateTM || "N/A",
- askRateTM: currencyData.askRateTM || "N/A",
- inputDate: currencyData.inputDate || "N/A",
- }
- } catch (err) {
- console.error(`Techcombank fetch failed for ${date}`, err)
- return null
- }
- }
-
- const fetchBIDVRate = async (date: string): Promise => {
- try {
- const res = await fetch(`/api/exchange/bidv?date=${date}`)
- if (!res.ok) {
- if (res.status === 404) {
- return null
- }
- console.error(`BIDV API returned ${res.status} for ${date}`)
- return null
- }
-
- const result = await res.json()
- const currencyData = result.data?.find((item: any) => item.currency === currency)
- if (!currencyData) return null
-
- return {
- date,
- bank: "BIDV",
- currency,
- askRate: currencyData.ban || "N/A",
- bidRateCK: currencyData.muaCk || "N/A",
- bidRateTM: currencyData.muaTm || "N/A",
- askRateTM: currencyData.ban || "N/A",
- inputDate: `${result.day_vi} ${result.hour}` || "N/A",
- }
- } catch (err) {
- console.error(`BIDV fetch failed for ${date}`, err)
- return null
- }
- }
-
- const fetchExchangeRate = async (date: string): Promise => {
- if (bank === "techcombank") {
- return fetchTechcombankRate(date)
- } else {
- return fetchBIDVRate(date)
- }
- }
-
- const handleFetchData = async () => {
- if (!startDate || !endDate) {
- setError("Please select both start and end dates")
- return
- }
-
- if (new Date(startDate) > new Date(endDate)) {
- setError("Start date must be before end date")
- return
- }
-
- setLoading(true)
- setError("")
- setData([])
-
- try {
- const dates = generateDateRange(startDate, endDate)
- const results: ExchangeRateData[] = []
-
- for (const date of dates) {
- const rateData = await fetchExchangeRate(date)
- if (rateData) {
- results.push(rateData)
- }
- // Add a small delay to avoid overwhelming the API
- await new Promise((resolve) => setTimeout(resolve, 200))
- }
-
- setData(results)
- if (results.length === 0) {
- setError("No data found for the selected date range")
- }
- } catch (error) {
- setError("An error occurred while fetching data")
- console.error(error)
- } finally {
- setLoading(false)
- }
- }
-
- const exportToCSV = () => {
- if (data.length === 0) return
-
- const headers = ["Date", "Bank", "Currency", "Ask Rate", "Bid Rate CK", "Bid Rate TM", "Ask Rate TM", "Input Date"]
- const csvContent = [
- headers.join(","),
- ...data.map((row) =>
- [
- row.date,
- row.bank,
- row.currency,
- row.askRate,
- row.bidRateCK,
- row.bidRateTM,
- row.askRateTM,
- row.inputDate,
- ].join(","),
- ),
- ].join("\n")
-
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" })
- const link = document.createElement("a")
- const url = URL.createObjectURL(blob)
- link.setAttribute("href", url)
- link.setAttribute(
- "download",
- `exchange_rates_${bank}_${currency.replace(/[^a-zA-Z0-9]/g, "_")}_${startDate}_to_${endDate}.csv`,
- )
- link.style.visibility = "hidden"
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- }
-
- const exportToXLSX = async () => {
- if (data.length === 0) return
-
- // toFile triggers the browser download, so no manual object URL is needed.
- try {
- await writeXlsxFile(data, {
- columns: XLSX_COLUMNS,
- sheet: "Exchange Rates",
- }).toFile(
- `exchange_rates_${bank}_${currency.replace(/[^a-zA-Z0-9]/g, "_")}_${startDate}_to_${endDate}.xlsx`,
- )
- } catch (error) {
- setError("An error occurred while exporting to XLSX")
- console.error(error)
- }
- }
-
- const formatNumber = (value: string) => {
- if (value === "N/A" || !value || value === "-") return value
- // Remove commas and parse
- const numValue = Number.parseFloat(value.replace(/,/g, ""))
- if (isNaN(numValue)) return value
- return numValue.toLocaleString()
- }
-
- const currentCurrencies = BANK_CURRENCIES[bank]
-
- return (
-
-
-
- Vietnam Bank Exchange Rate Exporter
-
- Fetch exchange rates from Vietnamese banks for a date range and export to CSV or XLSX
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setStartDate(e.target.value)} />
-
-
-
- setEndDate(e.target.value)} />
-
-
-
- {error && {error}
}
-
-
-
- {data.length > 0 && (
-
-
-
-
- Exchange Rate Data
-
- {currency} rates from {bank.toUpperCase()} from {startDate} to {endDate} ({data.length} records)
-
-
-
-
-
-
-
-
-
-
-
-
-
- Date
- Bank
- Currency
- Ask Rate
- Bid Rate CK
- Bid Rate TM
- Ask Rate TM
- Input Date
-
-
-
- {data.map((row, index) => (
-
- {row.date}
- {row.bank}
- {row.currency}
- {formatNumber(row.askRate)}
- {formatNumber(row.bidRateCK)}
- {formatNumber(row.bidRateTM)}
- {formatNumber(row.askRateTM)}
- {row.inputDate}
-
- ))}
-
-
-
-
-
- )}
-
- )
-}
diff --git a/components.json b/components.json
deleted file mode 100644
index d9ef0ae..0000000
--- a/components.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
- "rsc": true,
- "tsx": true,
- "tailwind": {
- "config": "tailwind.config.ts",
- "css": "app/globals.css",
- "baseColor": "neutral",
- "cssVariables": true,
- "prefix": ""
- },
- "aliases": {
- "components": "@/components",
- "utils": "@/lib/utils",
- "ui": "@/components/ui",
- "lib": "@/lib",
- "hooks": "@/hooks"
- },
- "iconLibrary": "lucide"
-}
\ No newline at end of file
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
deleted file mode 100644
index 55c2f6e..0000000
--- a/components/theme-provider.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import {
- ThemeProvider as NextThemesProvider,
- type ThemeProviderProps,
-} from 'next-themes'
-
-export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
- return {children}
-}
diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx
deleted file mode 100644
index 24c788c..0000000
--- a/components/ui/accordion.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import { ChevronDown } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const Accordion = AccordionPrimitive.Root
-
-const AccordionItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AccordionItem.displayName = "AccordionItem"
-
-const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180",
- className
- )}
- {...props}
- >
- {children}
-
-
-
-))
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
-
-const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- {children}
-
-))
-
-AccordionContent.displayName = AccordionPrimitive.Content.displayName
-
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx
deleted file mode 100644
index 25e7b47..0000000
--- a/components/ui/alert-dialog.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-"use client"
-
-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,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
-
-const AlertDialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-))
-AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
-
-const AlertDialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-AlertDialogHeader.displayName = "AlertDialogHeader"
-
-const AlertDialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-AlertDialogFooter.displayName = "AlertDialogFooter"
-
-const AlertDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
-
-const AlertDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogDescription.displayName =
- AlertDialogPrimitive.Description.displayName
-
-const AlertDialogAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
-
-const AlertDialogCancel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
-
-export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
-}
diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx
deleted file mode 100644
index 41fa7e0..0000000
--- a/components/ui/alert.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const alertVariants = cva(
- "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
- {
- variants: {
- variant: {
- default: "bg-background text-foreground",
- destructive:
- "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-const Alert = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & VariantProps
->(({ className, variant, ...props }, ref) => (
-
-))
-Alert.displayName = "Alert"
-
-const AlertTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-AlertTitle.displayName = "AlertTitle"
-
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-AlertDescription.displayName = "AlertDescription"
-
-export { Alert, AlertTitle, AlertDescription }
diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx
deleted file mode 100644
index d6a5226..0000000
--- a/components/ui/aspect-ratio.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-"use client"
-
-import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
-
-const AspectRatio = AspectRatioPrimitive.Root
-
-export { AspectRatio }
diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx
deleted file mode 100644
index 51e507b..0000000
--- a/components/ui/avatar.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
-
-import { cn } from "@/lib/utils"
-
-const Avatar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-Avatar.displayName = AvatarPrimitive.Root.displayName
-
-const AvatarImage = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AvatarImage.displayName = AvatarPrimitive.Image.displayName
-
-const AvatarFallback = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
-
-export { Avatar, AvatarImage, AvatarFallback }
diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx
deleted file mode 100644
index f000e3e..0000000
--- a/components/ui/badge.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const badgeVariants = cva(
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
- {
- variants: {
- variant: {
- default:
- "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
- secondary:
- "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
- destructive:
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
- outline: "text-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-export interface BadgeProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-function Badge({ className, variant, ...props }: BadgeProps) {
- return (
-
- )
-}
-
-export { Badge, badgeVariants }
diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx
deleted file mode 100644
index 60e6c96..0000000
--- a/components/ui/breadcrumb.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { ChevronRight, MoreHorizontal } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<"nav"> & {
- separator?: React.ReactNode
- }
->(({ ...props }, ref) => )
-Breadcrumb.displayName = "Breadcrumb"
-
-const BreadcrumbList = React.forwardRef<
- HTMLOListElement,
- React.ComponentPropsWithoutRef<"ol">
->(({ className, ...props }, ref) => (
-
-))
-BreadcrumbList.displayName = "BreadcrumbList"
-
-const BreadcrumbItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentPropsWithoutRef<"li">
->(({ className, ...props }, ref) => (
-
-))
-BreadcrumbItem.displayName = "BreadcrumbItem"
-
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<"a"> & {
- asChild?: boolean
- }
->(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a"
-
- return (
-
- )
-})
-BreadcrumbLink.displayName = "BreadcrumbLink"
-
-const BreadcrumbPage = React.forwardRef<
- HTMLSpanElement,
- React.ComponentPropsWithoutRef<"span">
->(({ className, ...props }, ref) => (
-
-))
-BreadcrumbPage.displayName = "BreadcrumbPage"
-
-const BreadcrumbSeparator = ({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) => (
- svg]:w-3.5 [&>svg]:h-3.5", className)}
- {...props}
- >
- {children ?? }
-
-)
-BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
-
-const BreadcrumbEllipsis = ({
- className,
- ...props
-}: React.ComponentProps<"span">) => (
-
-
- More
-
-)
-BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
-
-export {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
- BreadcrumbEllipsis,
-}
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
deleted file mode 100644
index 36496a2..0000000
--- a/components/ui/button.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive:
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline:
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
- secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean
-}
-
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
- return (
-
- )
- }
-)
-Button.displayName = "Button"
-
-export { Button, buttonVariants }
diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx
deleted file mode 100644
index 61d2b45..0000000
--- a/components/ui/calendar.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { ChevronLeft, ChevronRight } from "lucide-react"
-import { DayPicker } from "react-day-picker"
-
-import { cn } from "@/lib/utils"
-import { buttonVariants } from "@/components/ui/button"
-
-export type CalendarProps = React.ComponentProps
-
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
-}: CalendarProps) {
- return (
- ,
- IconRight: ({ ...props }) => ,
- }}
- {...props}
- />
- )
-}
-Calendar.displayName = "Calendar"
-
-export { Calendar }
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
deleted file mode 100644
index f62edea..0000000
--- a/components/ui/card.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-const Card = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-Card.displayName = "Card"
-
-const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardHeader.displayName = "CardHeader"
-
-const CardTitle = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardTitle.displayName = "CardTitle"
-
-const CardDescription = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardDescription.displayName = "CardDescription"
-
-const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardContent.displayName = "CardContent"
-
-const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardFooter.displayName = "CardFooter"
-
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx
deleted file mode 100644
index ec505d0..0000000
--- a/components/ui/carousel.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-"use client"
-
-import * as React from "react"
-import useEmblaCarousel, {
- type UseEmblaCarouselType,
-} from "embla-carousel-react"
-import { ArrowLeft, ArrowRight } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-
-type CarouselApi = UseEmblaCarouselType[1]
-type UseCarouselParameters = Parameters
-type CarouselOptions = UseCarouselParameters[0]
-type CarouselPlugin = UseCarouselParameters[1]
-
-type CarouselProps = {
- opts?: CarouselOptions
- plugins?: CarouselPlugin
- orientation?: "horizontal" | "vertical"
- setApi?: (api: CarouselApi) => void
-}
-
-type CarouselContextProps = {
- carouselRef: ReturnType[0]
- api: ReturnType[1]
- scrollPrev: () => void
- scrollNext: () => void
- canScrollPrev: boolean
- canScrollNext: boolean
-} & CarouselProps
-
-const CarouselContext = React.createContext(null)
-
-function useCarousel() {
- const context = React.useContext(CarouselContext)
-
- if (!context) {
- throw new Error("useCarousel must be used within a ")
- }
-
- return context
-}
-
-const Carousel = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & CarouselProps
->(
- (
- {
- orientation = "horizontal",
- opts,
- setApi,
- plugins,
- className,
- children,
- ...props
- },
- ref
- ) => {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === "horizontal" ? "x" : "y",
- },
- plugins
- )
- const [canScrollPrev, setCanScrollPrev] = React.useState(false)
- const [canScrollNext, setCanScrollNext] = React.useState(false)
-
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) {
- return
- }
-
- setCanScrollPrev(api.canScrollPrev())
- setCanScrollNext(api.canScrollNext())
- }, [])
-
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev()
- }, [api])
-
- const scrollNext = React.useCallback(() => {
- api?.scrollNext()
- }, [api])
-
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault()
- scrollPrev()
- } else if (event.key === "ArrowRight") {
- event.preventDefault()
- scrollNext()
- }
- },
- [scrollPrev, scrollNext]
- )
-
- React.useEffect(() => {
- if (!api || !setApi) {
- return
- }
-
- setApi(api)
- }, [api, setApi])
-
- React.useEffect(() => {
- if (!api) {
- return
- }
-
- onSelect(api)
- api.on("reInit", onSelect)
- api.on("select", onSelect)
-
- return () => {
- api?.off("select", onSelect)
- }
- }, [api, onSelect])
-
- return (
-
-
- {children}
-
-
- )
- }
-)
-Carousel.displayName = "Carousel"
-
-const CarouselContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { carouselRef, orientation } = useCarousel()
-
- return (
-
- )
-})
-CarouselContent.displayName = "CarouselContent"
-
-const CarouselItem = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { orientation } = useCarousel()
-
- return (
-
- )
-})
-CarouselItem.displayName = "CarouselItem"
-
-const CarouselPrevious = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps
->(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
-
- return (
-
- )
-})
-CarouselPrevious.displayName = "CarouselPrevious"
-
-const CarouselNext = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps
->(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollNext, canScrollNext } = useCarousel()
-
- return (
-
- )
-})
-CarouselNext.displayName = "CarouselNext"
-
-export {
- type CarouselApi,
- Carousel,
- CarouselContent,
- CarouselItem,
- CarouselPrevious,
- CarouselNext,
-}
diff --git a/components/ui/chart.tsx b/components/ui/chart.tsx
deleted file mode 100644
index 8620baa..0000000
--- a/components/ui/chart.tsx
+++ /dev/null
@@ -1,365 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as RechartsPrimitive from "recharts"
-
-import { cn } from "@/lib/utils"
-
-// Format: { THEME_NAME: CSS_SELECTOR }
-const THEMES = { light: "", dark: ".dark" } as const
-
-export type ChartConfig = {
- [k in string]: {
- label?: React.ReactNode
- icon?: React.ComponentType
- } & (
- | { color?: string; theme?: never }
- | { color?: never; theme: Record }
- )
-}
-
-type ChartContextProps = {
- config: ChartConfig
-}
-
-const ChartContext = React.createContext(null)
-
-function useChart() {
- const context = React.useContext(ChartContext)
-
- if (!context) {
- throw new Error("useChart must be used within a ")
- }
-
- return context
-}
-
-const ChartContainer = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & {
- config: ChartConfig
- children: React.ComponentProps<
- typeof RechartsPrimitive.ResponsiveContainer
- >["children"]
- }
->(({ id, className, children, config, ...props }, ref) => {
- const uniqueId = React.useId()
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
-
- return (
-
-
-
-
- {children}
-
-
-
- )
-})
-ChartContainer.displayName = "Chart"
-
-const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
- const colorConfig = Object.entries(config).filter(
- ([_, config]) => config.theme || config.color
- )
-
- if (!colorConfig.length) {
- return null
- }
-
- return (
-