commit f0c0f3b8d66718ebbbc1e14b743ff00c453ee3b0 Author: jungwoo choi Date: Wed Oct 22 09:31:15 2025 +0900 Initial commit: SAPIENS Stock service diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..7d65fa4 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37c2b6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# 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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1703686 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies +COPY package.json pnpm-lock.yaml* ./ +RUN corepack enable pnpm && pnpm i + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build the application +RUN corepack enable pnpm && pnpm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..8522524 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Tesla stock analysis + +*Automatically synced with your [v0.app](https://v0.app) deployments* + +[![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/jongwhankim-5870s-projects/v0-tesla-stock-analysis) +[![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/projects/FtJJ2onqZ24) + +## Overview + +This repository will stay in sync with your deployed chats on [v0.app](https://v0.app). +Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app). + +## Deployment + +Your project is live at: + +**[https://vercel.com/jongwhankim-5870s-projects/v0-tesla-stock-analysis](https://vercel.com/jongwhankim-5870s-projects/v0-tesla-stock-analysis)** + +## Build your app + +Continue building your app on: + +**[https://v0.app/chat/projects/FtJJ2onqZ24](https://v0.app/chat/projects/FtJJ2onqZ24)** + +## How It Works + +1. Create and modify your project using [v0.app](https://v0.app) +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 diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..a2d902c --- /dev/null +++ b/app/globals.css @@ -0,0 +1,146 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + /* Light mode colors - bright backgrounds, dark text */ + --background: oklch(0.98 0 0); + --foreground: oklch(0.15 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.15 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.15 0 0); + --primary: oklch(0.4 0.2 220); + --primary-foreground: oklch(0.98 0 0); + --secondary: oklch(0.95 0 0); + --secondary-foreground: oklch(0.15 0 0); + --muted: oklch(0.95 0 0); + --muted-foreground: oklch(0.45 0 0); + --accent: oklch(0.95 0 0); + --accent-foreground: oklch(0.15 0 0); + --destructive: oklch(0.6 0.2 15); + --destructive-foreground: oklch(0.98 0 0); + --success: oklch(0.6 0.15 140); + --success-foreground: oklch(0.98 0 0); + --border: oklch(0.9 0 0); + --input: oklch(1 0 0); + --ring: oklch(0.4 0.2 220); + --chart-1: oklch(0.4 0.2 220); + --chart-2: oklch(0.6 0.25 15); + --chart-3: oklch(0.5 0.2 140); + --chart-4: oklch(0.6 0.25 60); + --chart-5: oklch(0.5 0.22 300); + --radius: 0.5rem; + --sidebar: oklch(0.98 0 0); + --sidebar-foreground: oklch(0.15 0 0); + --sidebar-primary: oklch(0.4 0.2 220); + --sidebar-primary-foreground: oklch(0.98 0 0); + --sidebar-accent: oklch(0.95 0 0); + --sidebar-accent-foreground: oklch(0.15 0 0); + --sidebar-border: oklch(0.9 0 0); + --sidebar-ring: oklch(0.4 0.2 220); +} + +.dark { + /* Dark mode colors - dark backgrounds, light text */ + --background: oklch(0.08 0 0); + --foreground: oklch(0.95 0 0); + --card: oklch(0.12 0 0); + --card-foreground: oklch(0.95 0 0); + --popover: oklch(0.12 0 0); + --popover-foreground: oklch(0.95 0 0); + --primary: oklch(0.8 0.2 220); + --primary-foreground: oklch(0.95 0 0); + --secondary: oklch(0.18 0 0); + --secondary-foreground: oklch(0.85 0 0); + --muted: oklch(0.15 0 0); + --muted-foreground: oklch(0.75 0 0); + --accent: oklch(0.2 0 0); + --accent-foreground: oklch(0.9 0 0); + --destructive: oklch(0.6 0.2 15); + --destructive-foreground: oklch(0.95 0 0); + --success: oklch(0.6 0.15 140); + --success-foreground: oklch(0.95 0 0); + --border: oklch(0.35 0 0); + --input: oklch(0.18 0 0); + --ring: oklch(0.8 0.2 220); + --chart-1: oklch(0.8 0.2 220); + --chart-2: oklch(0.75 0.25 15); + --chart-3: oklch(0.75 0.2 140); + --chart-4: oklch(0.8 0.25 60); + --chart-5: oklch(0.75 0.22 300); + --sidebar: oklch(0.1 0 0); + --sidebar-foreground: oklch(0.9 0 0); + --sidebar-primary: oklch(0.8 0.2 220); + --sidebar-primary-foreground: oklch(0.95 0 0); + --sidebar-accent: oklch(0.15 0 0); + --sidebar-accent-foreground: oklch(0.85 0 0); + --sidebar-border: oklch(0.2 0 0); + --sidebar-ring: oklch(0.8 0.2 220); +} + +@theme inline { + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +@layer utilities { + .scrollbar-hide { + /* Hide scrollbar for IE, Edge and Firefox */ + -ms-overflow-style: none; + scrollbar-width: none; + } + + .scrollbar-hide::-webkit-scrollbar { + /* Hide scrollbar for Chrome, Safari and Opera */ + display: none; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..c4acdcb --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,33 @@ +import type React from "react" +import type { Metadata } from "next" +import { GeistSans } from "geist/font/sans" +import { GeistMono } from "geist/font/mono" +import { Analytics } from "@vercel/analytics/next" +import "./globals.css" +import { Suspense } from "react" +import { ThemeProvider } from "@/components/theme-provider" + +export const metadata: Metadata = { + title: "Stock Market Dashboard", + description: "Professional stock market data and analysis platform", + generator: "v0.app", +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + + + {children} + + + + + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..4034f36 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,5 @@ +import { StockDashboard } from "@/components/stock-dashboard" + +export default function Home() { + return +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..4ee62ee --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/components/company-info.tsx b/components/company-info.tsx new file mode 100644 index 0000000..507fdcf --- /dev/null +++ b/components/company-info.tsx @@ -0,0 +1,82 @@ +"use client" +import { Button } from "@/components/ui/button" + +interface CompanyInfoProps { + symbol: string +} + +export function CompanyInfo({ symbol }: CompanyInfoProps) { + // Mock company data + const companyData = { + TSLA: { + symbol: "TSLA", + marketCap: "$1.4T", + ipoDate: "Jun 29, 2010", + ceo: "Elon R. Musk", + employees: "126K", + sector: "Consumer Cyclical", + industry: "Auto - Manufacturers", + country: "US", + exchange: "NASDAQ Global Select", + description: + "Tesla, Inc. is a leading global manufacturer of electric vehicles and clean energy products, headquartered in Austin, Texas. The company operates in two main segments: Automotive, which includes the...", + readMore: true, + }, + } + + const company = companyData[symbol as keyof typeof companyData] || companyData.TSLA + + return ( +
+
+
+
+ Symbol + {company.symbol} +
+
+ Market Cap + {company.marketCap} +
+
+ IPO Date + {company.ipoDate} +
+
+ CEO + {company.ceo} +
+
+ Fulltime Employees + {company.employees} +
+
+ Sector + {company.sector} +
+
+ Industry + {company.industry} +
+
+ Country + {company.country} +
+
+ Exchange + {company.exchange} +
+
+ +
+

{company.description}

+ {company.readMore && ( + + )} +
+
+
+ ) +} diff --git a/components/earnings.tsx b/components/earnings.tsx new file mode 100644 index 0000000..e0be84c --- /dev/null +++ b/components/earnings.tsx @@ -0,0 +1,148 @@ +"use client" + +import { useState } from "react" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { Button } from "@/components/ui/button" +import { Info } from "lucide-react" + +const quarterlyData = [ + { quarter: "Q3 2025", status: "Upcoming", change: null, color: "text-muted-foreground" }, + { quarter: "Q2 2025", status: "-30.77%", change: -30.77, color: "text-red-400" }, + { quarter: "Q1 2025", status: "-65.91%", change: -65.91, color: "text-red-400" }, + { quarter: "Q4 2024", status: "-12%", change: -12, color: "text-red-400" }, + { quarter: "Q3 2024", status: "+6.9%", change: 6.9, color: "text-green-400" }, + { quarter: "Q2 2024", status: "-32.26%", change: -32.26, color: "text-red-400" }, + { quarter: "Q1 2024", status: "", change: null, color: "text-muted-foreground" }, +] + +const analysisPoints = [ + "Tesla successfully launched its robo-taxi service with paying customers in Austin, plans hyper-exponential expansion to additional US cities pending regulatory approval, and targets covering half the US population by year-end; expected material financial impact by end of next year, with long-term robotaxi cost per mile potentially as low as $0.25-$0.30 for purpose-built vehicles.", + "Adoption of Full Self-Driving (FSD) increased 25% since v12 launch and with new lower subscription pricing; Tesla reports vehicles on FSD are 10x safer than without, and further expansion of FSD—including unsupervised personal use—expected in certain geographies by year-end.", + "Lower-cost Tesla vehicle production began in H1 2025; ramping will be slower than initially expected due to focus on maximizing deliveries before US EV tax credits expire and the additional complexity of new models, with broader availability anticipated in Q4.", + "Tesla's Optimus humanoid robot v3 prototype will begin production at the start of next year, with scale targeted at 100,000 robots per month within five years; Optimus is anticipated to be Tesla's biggest product ever, with initial revenues expected to be immaterial until production ramps.", + "Near-term challenges include loss of US EV aid storage tax credits and increased tariffs, affecting both revenue and costs; CapEx for 2025 is expected to exceed $9 billion, driven by investment in AI, manufacturing, and energy storage—with record Powerwall deployments and strong MegaPack demand despite policy/tariff headwinds.", +] + +export function Earnings() { + const [activeSubTab, setActiveSubTab] = useState("analysis") + + return ( +
+ {/* Quarterly Timeline */} +
+ {quarterlyData.map((quarter, index) => ( + + ))} +
+ + {/* Key Metrics */} +
+
+
+
Report Date
+
Jul 24, 2025
+
+
+
Report Period
+
Q2 2025
+
+
+ +
+
+
+
+ Reported Revenue +
+ Avg 1d Change +
+
+
22억원
+
+
+
Beat +0.97%
+
10%
+
+
+ +
+
+
+ Reported EPS +
+ Implied Move 1d +
+
+
0.27
+
+
+
Miss -30.77%
+
6.12%
+
+
+
+
+ + {/* Sub Navigation */} + + + + Analysis + + + Transcript + + + Documents + + + + +
+ {analysisPoints.map((point, index) => ( +
+
+

{point}

+
+ ))} +
+ + + +
+ Earnings call transcript will be available after the call. +
+
+ + +
+ SEC filings and related documents will be available here. +
+
+ + + {/* Footer */} +
+ Financial information provided by Financial Modeling Prep. Options data provided by Unusual Whales. Live + transcripts provided by Quartz. Reported revenue and EPS data from Earnings powered by Fiscal.ai. All data is + provided for informational purposes only, and is not intended for trading purposes or financial, investment, + tax, legal, accounting or other advice. +
+
+ ) +} diff --git a/components/financials.tsx b/components/financials.tsx new file mode 100644 index 0000000..3a2c8d1 --- /dev/null +++ b/components/financials.tsx @@ -0,0 +1,362 @@ +"use client" + +import { useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Download, ChevronLeft, ChevronRight, TrendingUp, Minus, TrendingDown } from "lucide-react" + +const financialTabs = [ + { id: "key-stats", label: "Key Stats" }, + { id: "income-statement", label: "Income Statement" }, + { id: "balance-sheet", label: "Balance Sheet" }, + { id: "cash-flow", label: "Cash Flow" }, +] + +const financialData = { + years: ["12/31/2025", "12/31/2024", "12/31/2023", "12/31/2022", "12/31/2021"], + metrics: [ + { + label: "Market Cap", + values: ["-", "$1,291,076", "$788,551", "$385,553", "$1,042,337"], + type: "currency", + }, + { + label: "- Cash", + values: ["-", "16,139", "16,398", "16,253", "17,576"], + type: "number", + indent: true, + }, + { + label: "+ Debt", + values: ["-", "13,623", "9,573", "5,748", "8,873"], + type: "number", + indent: true, + }, + { + label: "Enterprise Value", + values: ["-", "1,288,560", "781,726", "375,048", "1,033,634"], + type: "currency", + highlight: true, + }, + { + label: "Revenue", + values: ["92,915", "97,690", "96,773", "81,462", "53,823"], + type: "currency", + }, + { + label: "% Growth", + values: ["-4.9%", "0.9%", "18.8%", "51.4%", "-"], + type: "percentage", + indent: true, + italic: true, + }, + { + label: "Gross Profit", + values: ["15,912", "17,450", "17,660", "20,853", "13,606"], + type: "currency", + }, + { + label: "% Margin", + values: ["17.1%", "17.9%", "18.2%", "25.6%", "25.3%"], + type: "percentage", + indent: true, + italic: true, + }, + { + label: "EBITDA", + values: ["13,428", "14,708", "14,796", "17,657", "9,625"], + type: "currency", + }, + { + label: "% Margin", + values: ["14.5%", "15.1%", "15.3%", "21.7%", "17.9%"], + type: "percentage", + indent: true, + italic: true, + }, + { + label: "Net Income", + values: ["5,018", "7,130", "14,999", "12,583", "5,524"], + type: "currency", + }, + { + label: "% Margin", + values: ["5.4%", "7.3%", "15.5%", "15.4%", "10.3%"], + type: "percentage", + indent: true, + italic: true, + }, + { + label: "EPS Diluted", + values: ["3.84", "2.04", "4.31", "3.62", "1.63"], + type: "number", + }, + { + label: "% Growth", + values: ["88.0%", "-52.7%", "19.1%", "122.1%", "-"], + type: "percentage", + indent: true, + italic: true, + }, + { + label: "Operating Cash Flow", + values: ["12,484", "14,923", "13,256", "14,724", "11,497"], + type: "currency", + }, + { + label: "Capital Expenditures", + values: ["-9,714", "-11,342", "-8,899", "-7,172", "-8,014"], + type: "currency", + }, + { + label: "Free Cash Flow", + values: ["4,011", "3,581", "4,357", "7,552", "3,483"], + type: "currency", + highlight: true, + }, + ], +} + +const allInsights = [ + { + title: "Strong EV Market Leadership Position", + content: + "Tesla maintains its position as the world's leading electric vehicle manufacturer with consistent delivery growth and expanding global market presence, particularly in key markets like China and Europe.", + signal: "bullish", + }, + { + title: "Autonomous Driving Technology Advancement", + content: + "Significant progress in Full Self-Driving capabilities and neural network improvements position Tesla ahead of traditional automakers in the autonomous vehicle race.", + signal: "bullish", + }, + { + title: "Energy Business Expansion", + content: + "Tesla's energy storage and solar business continues to grow, providing diversification beyond automotive and creating additional revenue streams with higher margins.", + signal: "bullish", + }, + { + title: "Market Capitalization Volatility amid Industry Events", + content: + "Tesla's market capitalization fluctuated dramatically from $1.04 trillion in 2021 to a low of $385.6 billion in 2022, before rebounding to over $1.29 trillion by the end of 2024.", + signal: "neutral", + }, + { + title: "Revenue Growth Stabilization", + content: + "Revenue growth has stabilized after the rapid expansion phase, with modest growth rates indicating market maturation in core segments.", + signal: "neutral", + }, + { + title: "Production Capacity Utilization", + content: + "Current production facilities are operating at high capacity, requiring significant capital investment for further expansion to meet growing demand.", + signal: "neutral", + }, + { + title: "Compressed Gross Margins Post-2022", + content: + "Gross profit margins declined significantly from a peak of 25.6% in 2022 to 17.9% in 2024, indicating aggressive pricing cuts and increased production costs.", + signal: "bearish", + }, + { + title: "Increased Competition in EV Market", + content: + "Traditional automakers and new EV startups are rapidly expanding their electric vehicle offerings, intensifying competition and potentially impacting Tesla's market share.", + signal: "bearish", + }, + { + title: "Regulatory and Policy Uncertainty", + content: + "Changes in government EV incentives and regulations across different markets could impact demand and profitability in key regions.", + signal: "bearish", + }, +] + +export function Financials() { + const [activeFinancialTab, setActiveFinancialTab] = useState("key-stats") + const [period, setPeriod] = useState("annual") + const [currency, setCurrency] = useState("M") + const [currentInsightIndex, setCurrentInsightIndex] = useState(0) + + const insightsPerPage = 3 + const totalPages = Math.ceil(allInsights.length / insightsPerPage) + const currentInsights = allInsights.slice(currentInsightIndex, currentInsightIndex + insightsPerPage) + + const nextInsights = () => { + if (currentInsightIndex + insightsPerPage < allInsights.length) { + setCurrentInsightIndex(currentInsightIndex + insightsPerPage) + } + } + + const prevInsights = () => { + if (currentInsightIndex > 0) { + setCurrentInsightIndex(Math.max(0, currentInsightIndex - insightsPerPage)) + } + } + + const getSignalStyle = (signal: string) => { + switch (signal) { + case "bullish": + return { + icon: TrendingUp, + color: "text-green-500", + bg: "bg-green-500/10", + label: "Bullish Signal", + } + case "bearish": + return { + icon: TrendingDown, + color: "text-red-500", + bg: "bg-red-500/10", + label: "Bearish Signal", + } + default: + return { + icon: Minus, + color: "text-gray-500", + bg: "bg-gray-500/10", + label: "Neutral Effect", + } + } + } + + return ( +
+ + +
+
+ {financialTabs.map((tab) => ( + + ))} +
+
+ + + +
+
+
+ +
+ + + + + {financialData.years.map((year) => ( + + ))} + + + + {financialData.metrics.map((metric, index) => ( + + + {metric.values.map((value, valueIndex) => ( + + ))} + + ))} + +
+ {year} +
+ {metric.label} + + {value} +
+
+
+
+ + + +
+ + 💡 + Insights + +
+ {allInsights.length} insights +
+
+
+
+
+ + + {Math.floor(currentInsightIndex / insightsPerPage) + 1} / {totalPages} + + +
+
+
+ +
+ {currentInsights.map((insight, index) => { + const signalStyle = getSignalStyle(insight.signal) + const IconComponent = signalStyle.icon + + return ( +
+
+ + {signalStyle.label} +
+

{insight.title}

+

{insight.content}

+
+ ) + })} +
+
+
+
+ ) +} diff --git a/components/historical-data.tsx b/components/historical-data.tsx new file mode 100644 index 0000000..7ad5150 --- /dev/null +++ b/components/historical-data.tsx @@ -0,0 +1,121 @@ +"use client" + +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader } from "@/components/ui/card" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" + +const timePeriods = [ + { value: "1D", label: "1D" }, + { value: "5D", label: "5D" }, + { value: "1M", label: "1M" }, + { value: "6M", label: "6M" }, + { value: "YTD", label: "YTD" }, + { value: "1Y", label: "1Y" }, + { value: "5Y", label: "5Y" }, + { value: "MAX", label: "MAX" }, +] + +const historicalData = [ + { date: "Sep 26, 2025", open: 428.3, high: 440.47, low: 421.02, close: 440.4, volume: "102M" }, + { date: "Aug 29, 2025", open: 347.23, high: 348.75, low: 331.7, close: 333.87, volume: "81M" }, + { date: "Jul 31, 2025", open: 319.61, high: 321.37, low: 306.1, close: 308.27, volume: "85M" }, + { date: "Jun 30, 2025", open: 319.9, high: 325.58, low: 316.6, close: 317.66, volume: "77M" }, + { date: "May 30, 2025", open: 355.52, high: 363.68, low: 345.29, close: 346.46, volume: "123M" }, + { date: "Apr 30, 2025", open: 279.9, high: 284.45, low: 270.78, close: 282.16, volume: "129M" }, + { date: "Mar 31, 2025", open: 249.31, high: 260.56, low: 243.36, close: 259.16, volume: "134M" }, + { date: "Feb 28, 2025", open: 279.5, high: 293.88, low: 273.6, close: 292.98, volume: "116M" }, + { date: "Jan 31, 2025", open: 401.53, high: 419.99, low: 401.34, close: 404.6, volume: "84M" }, + { date: "Dec 31, 2024", open: 423.79, high: 427.93, low: 402.54, close: 403.84, volume: "77M" }, + { date: "Nov 29, 2024", open: 336.08, high: 345.45, low: 334.65, close: 345.16, volume: "37M" }, + { date: "Oct 31, 2024", open: 257.99, high: 259.75, low: 249.25, close: 249.85, volume: "67M" }, + { date: "Sep 30, 2024", open: 259.04, high: 264.86, low: 256.77, close: 261.63, volume: "81M" }, + { date: "Aug 30, 2024", open: 208.63, high: 214.57, low: 207.03, close: 214.11, volume: "63M" }, + { date: "Jul 31, 2024", open: 227.9, high: 234.68, low: 226.79, close: 232.07, volume: "67M" }, + { date: "Jun 28, 2024", open: 199.55, high: 203.2, low: 195.26, close: 197.88, volume: "95M" }, + { date: "May 31, 2024", open: 178.5, high: 180.32, low: 173.82, close: 178.08, volume: "67M" }, + { date: "Apr 30, 2024", open: 186.98, high: 190.95, low: 182.84, close: 183.28, volume: "127M" }, + { date: "Mar 28, 2024", open: 177.45, high: 179.57, low: 175.3, close: 175.79, volume: "78M" }, + { date: "Feb 29, 2024", open: 204.18, high: 205.28, low: 198.45, close: 201.88, volume: "86M" }, + { date: "Jan 31, 2024", open: 187.0, high: 193.97, low: 185.85, close: 187.29, volume: "103M" }, + { date: "Dec 29, 2023", open: 255.1, high: 255.19, low: 247.43, close: 248.48, volume: "101M" }, + { date: "Nov 30, 2023", open: 245.14, high: 245.22, low: 236.91, close: 240.08, volume: "132M" }, + { date: "Oct 31, 2023", open: 196.12, high: 202.8, low: 194.07, close: 200.84, volume: "118M" }, + { date: "Sep 29, 2023", open: 250.0, high: 254.77, low: 246.35, close: 250.22, volume: "128M" }, +] + +export function HistoricalData() { + const [selectedPeriod, setSelectedPeriod] = useState("1M") + const [selectedRange, setSelectedRange] = useState("1 month") + + return ( + + +
+
+ {timePeriods.map((period) => ( + + ))} +
+
+ + +
+
+
+ +
+ + + + + + + + + + + + + {historicalData.map((row, index) => ( + + + + + + + + + ))} + +
DateOpenHighLowCloseVolume
{row.date}${row.open.toFixed(2)}${row.high.toFixed(2)}${row.low.toFixed(2)}${row.close.toFixed(2)}{row.volume}
+
+
+
+ ) +} diff --git a/components/key-issues.tsx b/components/key-issues.tsx new file mode 100644 index 0000000..fa0540a --- /dev/null +++ b/components/key-issues.tsx @@ -0,0 +1,135 @@ +"use client" + +import { useState } from "react" +import { ChevronDown, ChevronUp, TrendingUp, TrendingDown, Users } from "lucide-react" +import { Card } from "@/components/ui/card" + +interface KeyIssue { + id: string + title: string + bullishView: { + content: string + sources: number + } + bearishView: { + content: string + sources: number + } +} + +const keyIssues: KeyIssue[] = [ + { + id: "valuation", + title: "Valuation and profitability trajectory", + bullishView: { + content: + "Tesla commands a premium valuation due to its leadership in electric vehicles, rapid innovation, and expanding service segment. Analysts point to margin improvements and the potential for a profit inflection driven by AI and Robotaxi expansion that can justify high multiples.", + sources: 2, + }, + bearishView: { + content: + "Tesla's valuation is disconnected from fundamentals, with slowing revenue growth and a sharp decline in earnings. Elevated PE ratios are unsustainable, and the anticipated margin expansion may be limited by intensifying competition and the loss of regulatory credit revenue.", + sources: 2, + }, + }, + { + id: "robotaxi", + title: "Impact and timeline of autonomous Robotaxi rollout", + bullishView: { + content: + "Rapid progress in AI and regulatory approvals signal Tesla's robotaxi launch is imminent, creating new business lines and boosting revenue. Approval to test autonomous vehicles and CEO confidence suggest commercialization could begin as early as next year.", + sources: 2, + }, + bearishView: { + content: + "Milestones for mass-deployment of robotaxis are aggressive, with regulatory, technical, and safety hurdles likely delaying meaningful revenue. Some analysts see the Arizona pilot as incremental rather than transformative, and question whether full autonomy will be reached within the year.", + sources: 3, + }, + }, + { + id: "competition", + title: "Competitive pressures and market share outlook", + bullishView: { + content: + "Tesla remains at the forefront of EV innovation and is positioned to achieve record deliveries due to its global manufacturing scale and technological edge. International expansion and diversified offerings are expected to sustain market share and offset new entrants.", + sources: 2, + }, + bearishView: { + content: + "Tesla faces rising competition from established automakers and emerging Chinese EV rivals, which erodes pricing power and squeezes margins. Analysts argue that global EV saturation and pricing pressures will stunt growth and diminish profitability.", + sources: 1, + }, + }, +] + +export function KeyIssues() { + const [expandedItems, setExpandedItems] = useState>(new Set(["valuation"])) + + const toggleExpanded = (id: string) => { + const newExpanded = new Set(expandedItems) + if (newExpanded.has(id)) { + newExpanded.delete(id) + } else { + newExpanded.add(id) + } + setExpandedItems(newExpanded) + } + + return ( + +

Key Issues

+
+ {keyIssues.map((issue) => { + const isExpanded = expandedItems.has(issue.id) + return ( +
+ + + {isExpanded && ( +
+ {/* Bullish View */} +
+
+ + Bullish view +
+

{issue.bullishView.content}

+
+ + {issue.bullishView.sources} sources +
+
+ + {/* Bearish View */} +
+
+ + Bearish view +
+

{issue.bearishView.content}

+
+ + + {issue.bearishView.sources} source{issue.bearishView.sources !== 1 ? "s" : ""} + +
+
+
+ )} +
+ ) + })} +
+
+ ) +} diff --git a/components/latest-price-movement.tsx b/components/latest-price-movement.tsx new file mode 100644 index 0000000..ca42b5a --- /dev/null +++ b/components/latest-price-movement.tsx @@ -0,0 +1,88 @@ +"use client" + +import { TrendingUp, TrendingDown } from "lucide-react" + +export function LatestPriceMovement() { + const priceMovements = [ + { + date: "Sep 24", + price: "$442.79", + change: "+4.58%", + description: + "Tesla shares surged 4.6% after analysts raised their estimates for Q3 deliveries, anticipating figures well above consensus, amid upgrades and bullish price targets from major Wall Street firms. Analysts cited optimism over upcoming tax credit expirations and confidence in Tesla's advancements in AI and autonomous vehicles, fueling the rally.", + isPositive: true, + }, + { + date: "Sep 23", + price: "$425.85", + change: "-3.61%", + description: + "Tesla shares fell 3.8% as Federal Reserve Chair Jerome Powell's comments on high stock market valuations and cautious messaging on interest rate cuts spooked investors. Broader tech sector weakness, compounded by Tesla's stretched valuation, drove the selloff.", + isPositive: false, + }, + { + date: "Sep 22", + price: "$434.21", + change: "+1.96%", + description: + "Tesla stock rallied 2% alongside a tech-led market rebound, fueled by renewed enthusiasm around AI and anticipation for Tesla's upcoming self-driving technology and robotaxi announcements. The stock neared 2025 highs as market sentiment turned bullish again after last week's volatility.", + isPositive: true, + }, + { + date: "Sep 19", + price: "$426.27", + change: "-1.87%", + description: + "Tesla shares declined 1.9% as profit taking set in following a strong rebound in technology stocks earlier in the week, with little company-specific news driving the drop but general rotation out of high-momentum names.", + isPositive: false, + }, + { + date: "Sep 18", + price: "$416.88", + change: "+2.14%", + description: + "Shares fell 2.2% as investors reacted to skepticism over valuation following several downgrades from analysts warning about unsustainable multiples and risks from slowing growth in key international markets, particularly China and Europe.", + isPositive: true, + }, + { + date: "Sep 17", + price: "$408.65", + change: "+1.32%", + description: + "Tesla shares gained 2.2% amid a market-wide rally led by technology stocks and increased optimism about electric vehicle demand, ahead of anticipated Q3 delivery updates.", + isPositive: true, + }, + ] + + return ( +
+

Latest Price Movement

+ +
+ {priceMovements.map((movement, index) => ( +
+
+
{movement.date}
+
{movement.price}
+
+ +
+
+ {movement.isPositive ? : } + {movement.change} +
+
+ +
+

{movement.description}

+
+
+ ))} +
+
+ ) +} diff --git a/components/navigation.tsx b/components/navigation.tsx new file mode 100644 index 0000000..eb5fcf3 --- /dev/null +++ b/components/navigation.tsx @@ -0,0 +1,52 @@ +"use client" + +import { Input } from "@/components/ui/input" +import { Search } from "lucide-react" +import Image from "next/image" +import { useTheme } from "next-themes" +import { useEffect, useState } from "react" +import { ThemeToggle } from "@/components/theme-toggle" + +export function Navigation() { + const { theme, systemTheme } = useTheme() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + if (!mounted) { + return null + } + + const currentTheme = theme === "system" ? systemTheme : theme + const logoSrc = currentTheme === "dark" ? "/logo-white.svg" : "/logo-black.svg" + + return ( + + ) +} diff --git a/components/recent-developments.tsx b/components/recent-developments.tsx new file mode 100644 index 0000000..ff8425f --- /dev/null +++ b/components/recent-developments.tsx @@ -0,0 +1,71 @@ +"use client" + +export function RecentDevelopments() { + const developments = [ + { + id: 1, + title: "European Registrations Plummet Despite EV Boom", + summary: + "Tesla registrations crashed 23% year-over-year in August across Europe to 14,831 units, while the broader European electric vehicle market surged 26%. European Automobile Manufacturers' Association data shows Tesla's eight-month European registration...", + timeAgo: "10 hours ago", + type: "negative", + avatar: "🇪🇺", + }, + { + id: 2, + title: "Musk Acquires Billion-Dollar Stake After Compensation Vote", + summary: + "CEO Elon Musk purchased $1 billion worth of Tesla stock in mid-September, marking his first such acquisition since 2020. The move follows the board's proposed compensation package potentially worth $900 billion, which could make Musk the world's first...", + timeAgo: "4 hours ago", + type: "positive", + avatar: "💰", + }, + { + id: 3, + title: "CFO Taneja Divests Nearly Million Shares Recently", + summary: + "Tesla CFO Vaibhav Taneja sold shares worth approximately $918,000 in another recent divestment, marking the latest in a series of similar transactions over the past year. The sale occurs ahead of Tesla's November 5 annual shareholder meeting.", + timeAgo: "9 hours ago", + type: "neutral", + avatar: "👤", + }, + ] + + return ( +
+
+

Recent Developments

+ Updated 3 hours ago +
+ +
+ {developments.map((item) => ( +
+
+
+
+ {item.avatar} +
+
+
+
+ {item.type === "positive" &&
} + {item.type === "negative" &&
} + {item.type === "neutral" &&
} + {item.timeAgo} +
+
+
+ +

{item.title}

+ +

{item.summary}

+
+ ))} +
+
+ ) +} diff --git a/components/research.tsx b/components/research.tsx new file mode 100644 index 0000000..7322919 --- /dev/null +++ b/components/research.tsx @@ -0,0 +1,195 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Download, ExternalLink } from "lucide-react" + +const newsArticles = [ + { + title: "Tesla: Shares Rally as Robotaxi Testing Expands to Arizona", + author: "Seth Goldstein for Morningstar", + date: "September 23, 2025", + }, + { + title: "Tesla: Shares Soar on CEO Elon Musk's Share Purchase", + author: "Seth Goldstein for Morningstar", + date: "September 15, 2025", + }, + { + title: "Tesla: Shares Rally on Expanded Robotaxi Testing", + author: "Seth Goldstein for Morningstar", + date: "September 13, 2025", + }, + { + title: "Tesla: Shares Rise on Board Proposal for New Compensation Plan for CEO Elon Musk", + author: "Seth Goldstein for Morningstar", + date: "September 5, 2025", + }, + { + title: "Tesla: Board Announces New Pay Package for CEO Elon Musk", + author: "Seth Goldstein for Morningstar", + date: "August 5, 2025", + updated: "Updated August 8, 2025 at 7:49 AM", + }, + { + title: "Tesla Earnings: Affordable Vehicle to Enter Production by End of Year as Robotaxi Testing Underway", + author: "Seth Goldstein for Morningstar", + date: "July 24, 2025", + }, + { + title: "Tesla: Shares Fall as US Law Set To Eliminate EV Tax Credits Early; Musk Plans to Start New Party", + author: "Seth Goldstein for Morningstar", + date: "July 8, 2025", + updated: "Updated July 17, 2025 at 9:55 AM", + }, + { + title: "Tesla: Second-Quarter Deliveries Decline Confirms Our View for Lower 2025 Deliveries", + author: "Seth Goldstein for Morningstar", + date: "July 2, 2025", + }, + { + title: "Tesla: Robotaxi Begins Testing With Non-Tesla Employees but With Limitations", + author: "Seth Goldstein for Morningstar", + date: "June 23, 2025", + updated: "Updated July 2, 2025 at 8:12 AM", + }, + { + title: "Tesla: Shares Rise as CEO Elon Musk's Feud With US President Donald Trump Cools", + author: "Seth Goldstein for Morningstar", + date: "June 7, 2025", + updated: "Updated June 21, 2025 at 2:56 PM", + }, + { + title: "Tesla: Shares Sink as CEO Elon Musk Feuds With US President Donald Trump", + author: "Seth Goldstein for Morningstar", + date: "June 6, 2025", + }, +] + +const perplexityReports = [ + { + title: "TSLA Initiation of Coverage Report", + author: "Perplexity Labs", + date: "Generated September 29, 2025 at 6:02 PM", + }, +] + +const independentReports = [ + { + title: "Tesla - US EQUITY Research", + author: "DBS Bank", + date: "September 22, 2025", + outlook: "neutral", + }, + { + title: "Morningstar Equity Analyst Report", + author: "Morningstar", + date: "June 10, 2025", + outlook: "bearish", + }, + { + title: "Tesla Inc. (TSLA) Research Analysis", + author: "Goldman Sachs", + date: "September 15, 2025", + outlook: "neutral", + }, +] + +export function Research() { + return ( +
+ {/* News Articles */} +
+ {newsArticles.map((article, index) => ( + + +
+
+

{article.title}

+
+
+ By {article.author} + + {article.date} + {article.updated && ( + <> + + {article.updated} + + )} +
+
+ +
+ + + ))} +
+ + {/* Perplexity Labs Reports */} + {perplexityReports.map((report, index) => ( + + +
+
+
+

{report.title}

+ + BETA + +
+
+ By {report.author} • {report.date} +
+
+ +
+
+
+ ))} + + {/* Independent Research Reports */} + {independentReports.map((report, index) => ( + + +
+
+
+ +

{report.title}

+
+
+ By {report.author} + + {report.date} + + + Outlook: {report.outlook} + +
+
+ +
+
+
+ ))} +
+ ) +} diff --git a/components/stock-chart.tsx b/components/stock-chart.tsx new file mode 100644 index 0000000..c7ff89f --- /dev/null +++ b/components/stock-chart.tsx @@ -0,0 +1,163 @@ +"use client" + +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { XAxis, YAxis, ResponsiveContainer, Tooltip, Area, AreaChart } from "recharts" +import { useTheme } from "next-themes" + +interface StockChartProps { + symbol: string +} + +const timeRanges = [ + { label: "1D", value: "1D" }, + { label: "5D", value: "5D" }, + { label: "1M", value: "1M" }, + { label: "6M", value: "6M" }, + { label: "YTD", value: "YTD" }, + { label: "1Y", value: "1Y" }, + { label: "5Y", value: "5Y" }, + { label: "MAX", value: "MAX" }, +] + +// Mock chart data +const generateChartData = () => { + const data = [] + const basePrice = 420 + for (let i = 0; i < 100; i++) { + const time = new Date(Date.now() - (100 - i) * 60 * 60 * 1000).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }) + const price = basePrice + Math.sin(i / 10) * 20 + (Math.random() - 0.5) * 10 + data.push({ + time, + price: price.toFixed(2), + volume: Math.floor(Math.random() * 1000000), + }) + } + return data +} + +export function StockChart({ symbol }: StockChartProps) { + const [selectedRange, setSelectedRange] = useState("1D") + const [chartData] = useState(generateChartData()) + const { theme } = useTheme() + + const currentPrice = Number.parseFloat(chartData[chartData.length - 1]?.price || "0") + const previousPrice = Number.parseFloat(chartData[0]?.price || "0") + const isPositive = currentPrice > previousPrice + + const isDark = theme === "dark" + const axisColor = isDark ? "#e5e7eb" : "hsl(var(--muted-foreground))" + const strokeColor = isDark ? "#60a5fa" : "hsl(var(--primary))" + const gradientStopColor = isDark ? "#60a5fa" : "hsl(var(--primary))" + + return ( +
+
+
+
+ {timeRanges.map((range) => ( + + ))} +
+
+
+ + -US$19.40 + + + {isPositive ? "+" : ""} + {(((currentPrice - previousPrice) / previousPrice) * 100).toFixed(2)}% + +
+
+ +
+ + + + + + + + + + + [`$${value}`, "Price"]} + labelStyle={{ color: "hsl(var(--muted-foreground))" }} + /> + + + +
+ +
+
+ Prev Close +
$442.79
+
+
+ 52W Range +
$212.41 - $488.54
+
+
+ Market Cap +
$1.37T
+
+
+ Open +
$435.24
+
+
+ P/E Ratio +
199.71
+
+
+ Dividend Yield +
+
+
+ Day Range +
$419.08 - $435.35
+
+
+ Volume +
96M
+
+
+ EPS +
$2.12
+
+
+
+ ) +} diff --git a/components/stock-dashboard.tsx b/components/stock-dashboard.tsx new file mode 100644 index 0000000..5c3366f --- /dev/null +++ b/components/stock-dashboard.tsx @@ -0,0 +1,78 @@ +"use client" + +import { useState } from "react" +import { StockHeader } from "./stock-header" +import { StockChart } from "./stock-chart" +import { CompanyInfo } from "./company-info" +import { RecentDevelopments } from "./recent-developments" +import { LatestPriceMovement } from "./latest-price-movement" +import { Watchlist } from "./watchlist" +import { Navigation } from "./navigation" +import { KeyIssues } from "./key-issues" +import { HistoricalData } from "./historical-data" +import { Financials } from "./financials" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" +import { Earnings } from "./earnings" +import { Research } from "./research" + +export function StockDashboard() { + const [selectedStock, setSelectedStock] = useState("TSLA") + const [activeTab, setActiveTab] = useState("overview") + + return ( +
+ +
+
+
+
+ {/* Left main content area with tabs */} +
+ + + + + Overview + Historical Data + Financials + Earnings + Research + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {/* Right sidebar with company info and watchlist */} +
+ + +
+
+
+
+
+
+ ) +} diff --git a/components/stock-header.tsx b/components/stock-header.tsx new file mode 100644 index 0000000..2365f41 --- /dev/null +++ b/components/stock-header.tsx @@ -0,0 +1,136 @@ +"use client" + +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { TrendingUp, TrendingDown, Star } from "lucide-react" + +interface StockHeaderProps { + symbol: string + activeTab: string + onTabChange: (tab: string) => void +} + +export function StockHeader({ symbol, activeTab, onTabChange }: StockHeaderProps) { + // Mock data - in real app this would come from an API + const stockData = { + TSLA: { + name: "Tesla, Inc.", + symbol: "TSLA", + exchange: "NASDAQ", + price: 423.39, + afterHoursPrice: 417.13, + change: 18.4, + changePercent: 4.38, + afterHoursChange: -6.26, + afterHoursChangePercent: -1.48, + lastUpdate: "At close: Sep 25, 4:00 PM EDT", + afterHoursUpdate: "After hours: Sep 26, 6:08 AM EDT", + }, + } + + const stock = stockData[symbol as keyof typeof stockData] || stockData.TSLA + const isPositive = stock.change > 0 + const isAfterHoursPositive = stock.afterHoursChange > 0 + + const tabs = [ + { id: "overview", label: "Overview" }, + { id: "historical", label: "Historical Data" }, + { id: "financials", label: "Financials" }, + { id: "earnings", label: "Earnings" }, + { id: "research", label: "Research" }, + ] + + return ( +
+
+
+
+
+ T +
+
+

{stock.name}

+
+ {stock.symbol} + + {stock.exchange} + + 🇺🇸 + +
+
+
+ +
+ +
+ {/* Regular Hours */} +
+
+ US${stock.price.toFixed(2)} +
+ {isPositive ? ( + + ) : ( + + )} + US${Math.abs(stock.change).toFixed(2)} + + {isPositive ? "+" : ""} + {stock.changePercent.toFixed(2)}% + +
+
+

{stock.lastUpdate}

+
+ + {/* After Hours */} +
+
+ + US${stock.afterHoursPrice.toFixed(2)} + +
+ {isAfterHoursPositive ? ( + + ) : ( + + )} + + US${Math.abs(stock.afterHoursChange).toFixed(2)} + + + {isAfterHoursPositive ? "+" : ""} + {stock.afterHoursChangePercent.toFixed(2)}% + +
+
+

{stock.afterHoursUpdate}

+
+
+
+ +
+ {tabs.map((tab) => ( + + ))} +
+
+ ) +} diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..55c2f6e --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'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/theme-toggle.tsx b/components/theme-toggle.tsx new file mode 100644 index 0000000..c41db57 --- /dev/null +++ b/components/theme-toggle.tsx @@ -0,0 +1,37 @@ +"use client" + +import * as React from "react" +import { Moon, Sun } from "lucide-react" +import { useTheme } from "next-themes" + +import { Button } from "@/components/ui/button" + +export function ThemeToggle() { + const { theme, setTheme } = useTheme() + const [mounted, setMounted] = React.useState(false) + + React.useEffect(() => { + setMounted(true) + }, []) + + if (!mounted) { + return ( + + ) + } + + return ( + + ) +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..e538a33 --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDownIcon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..9704452 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +'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' + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..e6751ab --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,66 @@ +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 px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..40bb120 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..aa98465 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..fc4126b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,46 @@ +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 badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..1750ff2 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return