diff --git a/AGENTS.md b/AGENTS.md
index aee3620..cf200c7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,45 +1,48 @@
# Repository Guidelines
## Project Structure & Module Organization
-- `app/`: Next.js App Router pages, layouts, and global styles (`app/globals.css`, `app/editor-theme.css`).
-- `components/`: shared UI components and providers.
-- `hooks/`: reusable React hooks (keep hook names `useThing`).
-- `lib/`: utilities and shared helpers.
-- `public/`: static assets served by Next.js.
-- `test/`: unit and visual tests plus utilities and setup.
-- `src-tauri/`: Tauri desktop shell and Rust configuration.
+- `src/`: Solid frontend application.
+ - `src/components/`: shared Solid UI and feature components.
+ - `src/routes/`: route-level screens and global route styles.
+ - `src/state/`: app state stores.
+ - `src/lib/`: utilities, Tauri helpers, markdown helpers, and i18n.
+- `public/`: static assets served by Vite.
+- `src-tauri/`: Tauri desktop shell and Rust commands.
+- `test/`: Vitest and Playwright coverage, helpers, and docs.
## Build, Test, and Development Commands
-- `pnpm dev`: start the Next.js dev server (Turbopack).
-- `pnpm build`: production build.
-- `pnpm start`: serve the production build.
-- `pnpm lint`: run ESLint (`next lint`).
+- `pnpm dev`: start the Solid + Vite dev server.
+- `pnpm build`: production build and TypeScript check.
+- `pnpm start`: preview the production build.
+- `pnpm lint`: run TypeScript checking.
- `pnpm test`: run Vitest once.
- `pnpm test:watch`: Vitest watch mode.
- `pnpm test:ui`: Vitest UI.
- `pnpm test:visual`: Playwright visual regression tests.
- `pnpm test:visual:ui`: Playwright UI runner.
-- `npx tsx test/run-all-tests.ts`: run the full test suite with reporting.
+- `pnpm tauri dev`: run the desktop shell in development.
+- `pnpm tauri build`: build the desktop app.
## Coding Style & Naming Conventions
-- TypeScript + React (Next.js). Follow existing file patterns and keep diffs minimal.
-- Indentation: 2 spaces in TS/TSX; align JSX props vertically when multi-line.
-- Components: `PascalCase` exports; files are typically kebab-case (match the folder).
-- Hooks: functions named `useThing`, stored in `hooks/`.
-- Linting: follow ESLint rules from `eslint.config.mjs`; fix lint before PRs.
+- TypeScript + Solid. Keep diffs focused and consistent with nearby files.
+- Indentation: 2 spaces in TS/TSX and CSS.
+- Components: `PascalCase` exports; filenames are typically kebab-case or grouped by feature.
+- Shared state lives in `src/state/`; generic helpers live in `src/lib/`.
+- Prefer small composable helpers over framework-heavy abstractions.
## Testing Guidelines
-- Unit tests live in `test/components/*.test.tsx` (Vitest + React Testing Library).
-- Visual regression tests live in `test/visual/*.spec.ts` (Playwright).
-- Use `test/setup.ts` and `test/utils/` for shared helpers.
-- If visuals change intentionally, update snapshots: `pnpm test:visual --update-snapshots`.
+- Unit and integration tests live in `test/components`, `test/hooks`, and `test/lib`.
+- Visual regression tests live in `test/visual/*.spec.ts`.
+- Use `test/setup.ts` and `test/utils/` for shared test setup.
+- If visuals change intentionally, update snapshots with `pnpm test:visual --update-snapshots`.
+- WebKit snapshots are optional locally and can be enabled with `PLAYWRIGHT_ENABLE_WEBKIT=1`.
## Commit & Pull Request Guidelines
- Commit messages follow conventional commits: `type(scope): subject`.
- Examples: `chore(deps): ...`, `ci(test): ...`.
-- PRs should include: a concise description, linked issue (if any), and screenshots for UI changes.
-- Note test coverage in the PR body (e.g., `pnpm test`, `pnpm test:visual`).
+- PRs should include a concise description, linked issue if any, and screenshots for UI changes.
+- Mention validation in the PR body, for example `pnpm test`, `pnpm test:visual`, or `pnpm build`.
## Configuration Notes
-- App config is in `next.config.ts`; tooling config in `vitest.config.ts` and `playwright.config.ts`.
-- Prefer `pnpm` for dependency changes; keep `pnpm-lock.yaml` in sync.
+- Frontend config lives in `vite.config.ts`, `vitest.config.ts`, and `playwright.config.ts`.
+- Tauri config lives in `src-tauri/tauri.conf.json` and `src-tauri/Cargo.toml`.
+- Prefer `pnpm` for dependency changes and keep `pnpm-lock.yaml` in sync.
diff --git a/README.md b/README.md
index 85db0f3..fadee1c 100644
--- a/README.md
+++ b/README.md
@@ -6,265 +6,123 @@
[](https://opensource.org/licenses/MIT)
[](https://www.typescriptlang.org/)
-[](https://nextjs.org/)
-[](https://reactjs.org/)
+[](https://www.solidjs.com/)
+[](https://vite.dev/)
[](https://tauri.app/)
-[](https://pnpm.io/)
-[](http://makeapullrequest.com)
-
-[Features](#features) · [Installation](#installation) · [Documentation](#documentation) · [Contributing](#contributing)
----
-
## Overview
-**OnlyWrite** is a modern, distraction-free writing application built with efficiency and user experience at its core. Designed for writers, developers, and content creators who value clean interfaces and powerful functionality.
-
-Built with cutting-edge technologies including Next.js 15, React 19, Tauri 2.4, and TypeScript, OnlyWrite delivers a native desktop experience with web-level flexibility.
+OnlyWrite is a local-first desktop writing app built with **Solid**, **Vite**, **Tauri 2**, and **TypeScript**.
+It focuses on a distraction-free workspace, fast file-based writing, markdown preview, bilingual UI, and desktop-friendly workflows.
## Features
-### Core Writing Experience
-- **Markdown Editor**: Full-featured markdown editing with live preview
-- **Split View**: Edit and preview simultaneously with resizable panels
-- **Auto-save**: Never lose your work with automatic 5-second intervals
-- **File Management**: Organize your workspace with folder-based navigation
-- **Syntax Highlighting**: Beautiful code blocks with theme integration
-
-### User Interface
-- **Dark/Light Themes**: Seamless theme switching with consistent color harmony
-- **Responsive Design**: Works beautifully on desktop and mobile
-- **Accessibility First**: WCAG compliant with keyboard navigation and screen reader support
-- **Modern Components**: Built with Radix UI and Tailwind CSS for a polished experience
-
-### Platform Features
-- **Cross-platform**: Native desktop apps for Windows, macOS, and Linux via Tauri
-- **Local-first**: All your data stays on your device
-- **Fast Performance**: Optimized with Turbopack for lightning-fast dev and build times
-- **Internationalization**: Support for English and Chinese (more languages coming)
+- Markdown writing with live preview
+- Edit / Render / Split view modes
+- Local folder-based workspace
+- Manual save, restore, and autosave
+- Light and dark themes
+- English and Chinese UI
+- Optional S3 image upload configuration
+- Tauri desktop packaging for Windows, macOS, and Linux
-### Technical Highlights
-- **Type-safe**: Full TypeScript coverage for reliable code
-- **Tested**: Comprehensive test suite with Vitest and Playwright
-- **Modern Stack**: React 19, Next.js 15, Tailwind CSS 4
-- **Developer Experience**: Hot reload, ESLint, and pre-configured dev environment
+## Tech Stack
-## Installation
-
-### Prerequisites
-- Node.js 20+
-- pnpm (recommended package manager)
-- Rust (for Tauri desktop builds)
-
-### Quick Start
+### Frontend
+- Solid 1.9
+- Vite 7
+- Tailwind CSS 4
+- Lucide Solid
+- marked + DOMPurify
-```bash
-# Clone the repository
-git clone https://github.com/yourusername/OnlyWrite.git
-cd OnlyWrite
+### Desktop
+- Tauri 2
+- Rust
-# Install dependencies
-pnpm install
+### Testing
+- Vitest
+- Solid Testing Library
+- Playwright
-# Start development server
-pnpm dev
+## Project Structure
-# Open http://localhost:3000
+```text
+OnlyWrite/
+├── src/ # Solid frontend
+│ ├── components/ # Shared and feature components
+│ ├── lib/ # Utilities and Tauri helpers
+│ ├── routes/ # Route screens and route CSS
+│ └── state/ # Solid stores
+├── public/ # Static assets
+├── src-tauri/ # Tauri shell and Rust commands
+└── test/ # Unit and visual tests
```
-### Desktop App Development
+## Scripts
```bash
-# Install Tauri CLI
-cargo install tauri-cli
+# Frontend
+pnpm dev
+pnpm build
+pnpm start
+pnpm lint
-# Run desktop app in development
-pnpm tauri dev
+# Tests
+pnpm test
+pnpm test:watch
+pnpm test:ui
+pnpm test:visual
+pnpm test:visual:ui
-# Build desktop app for production
+# Desktop
+pnpm tauri dev
pnpm tauri build
```
-## Documentation
-
-### Project Structure
-
-```
-OnlyWrite/
-├── app/ # Next.js App Router pages and layouts
-├── components/ # React components
-│ ├── ui/ # Reusable UI components (Radix + Tailwind)
-│ └── ... # Feature-specific components
-├── hooks/ # Custom React hooks
-├── lib/ # Utility functions and helpers
-├── public/ # Static assets
-├── src-tauri/ # Tauri desktop shell
-├── test/ # Test suites
-│ ├── components/ # Component tests
-│ ├── lib/ # Utility tests
-│ ├── hooks/ # Hook tests
-│ └── visual/ # Visual regression tests
-└── ...
-```
-
-### Available Scripts
+## Development
```bash
-# Development
-pnpm dev # Start Next.js dev server with Turbopack
-pnpm build # Production build
-pnpm start # Serve production build
-pnpm lint # Run ESLint
-
-# Testing
-pnpm test # Run unit tests (Vitest)
-pnpm test:watch # Run tests in watch mode
-pnpm test:ui # Open Vitest UI
-pnpm test:visual # Run visual regression tests (Playwright)
-pnpm test:visual:ui # Open Playwright UI
-
-# Desktop
-pnpm tauri dev # Run Tauri desktop app
-pnpm tauri build # Build desktop app
+git clone https://github.com/yourusername/OnlyWrite.git
+cd OnlyWrite
+pnpm install
+pnpm dev
```
-### Coding Guidelines
-
-- **TypeScript**: All code must be type-safe
-- **Components**: Use PascalCase for component names
-- **Hooks**: Prefix with `use` (e.g., `useI18n`, `useS3Config`)
-- **Styling**: Use Tailwind CSS utility classes
-- **Testing**: Write tests for new features and bug fixes
-- **Commits**: Follow [Conventional Commits](https://www.conventionalcommits.org/)
-
-## Technology Stack
+Then open `http://localhost:3000`.
-### Frontend
-- **Framework**: Next.js 15 (App Router)
-- **UI Library**: React 19
-- **Styling**: Tailwind CSS 4
-- **Components**: Radix UI
-- **Icons**: Tabler Icons + Lucide React
-- **Editor**: MDXEditor
+For the desktop shell:
-### Desktop
-- **Framework**: Tauri 2.4
-- **Language**: Rust
-
-### Development
-- **Language**: TypeScript 5
-- **Package Manager**: pnpm
-- **Build Tool**: Turbopack
-- **Linting**: ESLint 9
-- **Testing**: Vitest + Playwright
-- **Testing Library**: React Testing Library
-
-### Key Dependencies
-- `next-themes` - Theme management
-- `react-markdown` - Markdown rendering
-- `react-resizable-panels` - Split view
-- `@dnd-kit` - Drag and drop
-- `sonner` - Toast notifications
-- `zod` - Schema validation
+```bash
+pnpm tauri dev
+```
## Testing
-OnlyWrite maintains high code quality with comprehensive test coverage:
-
-### Unit Tests (Vitest)
-- Component functionality tests
-- Hook behavior tests
-- Utility function tests
-- Theme consistency tests
-
-### Visual Regression Tests (Playwright)
-- Cross-browser screenshot comparisons
-- Theme switching validation
-- Responsive design verification
-
-### Running Tests
-
```bash
-# Run all tests
-npx tsx test/run-all-tests.ts
-
-# Run unit tests only
+# Unit tests
pnpm test
-# Run visual tests only
+# Visual regression snapshots
pnpm test:visual
-# Update visual snapshots
+# Update visual baselines
pnpm test:visual --update-snapshots
```
-## Contributing
-
-We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
-
-### Development Workflow
+By default visual tests run on Chromium. To enable WebKit locally after installing its system dependencies:
-1. Fork the repository
-2. Create a feature branch (`git checkout -b feature/amazing-feature`)
-3. Make your changes
-4. Write or update tests
-5. Ensure all tests pass (`pnpm test && pnpm test:visual`)
-6. Run linting (`pnpm lint`)
-7. Commit your changes (`git commit -m 'feat: add amazing feature'`)
-8. Push to the branch (`git push origin feature/amazing-feature`)
-9. Open a Pull Request
-
-### PR Guidelines
-
-- Include a clear description of the changes
-- Link related issues
-- Add screenshots for UI changes
-- Ensure tests pass and coverage is maintained
-- Follow the existing code style
+```bash
+PLAYWRIGHT_ENABLE_WEBKIT=1 pnpm test:visual
+```
-## Roadmap
+## Notes
-- [ ] Cloud sync support (S3, Dropbox, etc.)
-- [ ] Real-time collaboration
-- [ ] Plugin system
-- [ ] Custom themes
-- [ ] Export to PDF/DOCX
-- [ ] Version history
-- [ ] Mobile apps (iOS/Android)
-- [ ] More language support
+- `pnpm lint` currently runs TypeScript checking.
+- Visual snapshots live under `test/visual/theme-visual-regression.spec.ts-snapshots/`.
+- The repository has been cleaned up to remove the old React / TanStack / Radix frontend.
## License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
-
-## Acknowledgments
-
-**This project was developed with the assistance of AI tools**, including:
-- Code generation and refactoring assistance
-- Documentation writing
-- Test case generation
-- Code review suggestions
-
-All AI-generated code has been reviewed, tested, and validated by human developers.
-
-### Built With
-
-- [Next.js](https://nextjs.org/) - React framework
-- [Tauri](https://tauri.app/) - Desktop framework
-- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS
-- [Radix UI](https://www.radix-ui.com/) - Unstyled UI primitives
-- [MDXEditor](https://mdxeditor.dev/) - Markdown editor component
-- [Vitest](https://vitest.dev/) - Unit testing framework
-- [Playwright](https://playwright.dev/) - E2E and visual testing
-
----
-
-
-
-**Made with ❤️ by the OnlyWrite Team**
-
-[Report Bug](https://github.com/yourusername/OnlyWrite/issues) · [Request Feature](https://github.com/yourusername/OnlyWrite/issues)
-
-
+MIT
diff --git a/components.json b/components.json
deleted file mode 100644
index 5a3c750..0000000
--- a/components.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "new-york",
- "rsc": true,
- "tsx": true,
- "tailwind": {
- "config": "",
- "css": "app/globals.css",
- "baseColor": "zinc",
- "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/app-shell.tsx b/components/app-shell.tsx
deleted file mode 100644
index 2f10791..0000000
--- a/components/app-shell.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client"
-
-import React from "react"
-import { useI18n } from "@/hooks/useI18n"
-
-export function AppShell({ children }: { children: React.ReactNode }) {
- const { t } = useI18n()
-
- return (
- <>
- {/* Skip to main content link for keyboard navigation */}
-
- {t("a11y.skipToContent")}
-
-
- {/* ARIA live region for status announcements */}
-
-
- {/* ARIA live region for urgent announcements */}
-
-
- {children}
- >
- )
-}
diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx
deleted file mode 100644
index 7cea20b..0000000
--- a/components/app-sidebar.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {
- IconCamera,
- IconChartBar,
- IconDashboard,
- IconDatabase,
- IconFileAi,
- IconFileDescription,
- IconFileWord,
- IconFolder,
- IconHelp,
- IconInnerShadowTop,
- IconListDetails,
- IconReport,
- IconSearch,
- IconSettings,
- IconUsers,
-} from "@tabler/icons-react"
-
-import { NavDocuments } from "@/components/nav-documents"
-import { NavMain } from "@/components/nav-main"
-import { NavSecondary } from "@/components/nav-secondary"
-import { NavUser } from "@/components/nav-user"
-import {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarHeader,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
-
-const data = {
- user: {
- name: "shadcn",
- email: "m@example.com",
- avatar: "/avatars/shadcn.jpg",
- },
- navMain: [
- {
- title: "Dashboard",
- url: "#",
- icon: IconDashboard,
- },
- {
- title: "Lifecycle",
- url: "#",
- icon: IconListDetails,
- },
- {
- title: "Analytics",
- url: "#",
- icon: IconChartBar,
- },
- {
- title: "Projects",
- url: "#",
- icon: IconFolder,
- },
- {
- title: "Team",
- url: "#",
- icon: IconUsers,
- },
- ],
- navClouds: [
- {
- title: "Capture",
- icon: IconCamera,
- isActive: true,
- url: "#",
- items: [
- {
- title: "Active Proposals",
- url: "#",
- },
- {
- title: "Archived",
- url: "#",
- },
- ],
- },
- {
- title: "Proposal",
- icon: IconFileDescription,
- url: "#",
- items: [
- {
- title: "Active Proposals",
- url: "#",
- },
- {
- title: "Archived",
- url: "#",
- },
- ],
- },
- {
- title: "Prompts",
- icon: IconFileAi,
- url: "#",
- items: [
- {
- title: "Active Proposals",
- url: "#",
- },
- {
- title: "Archived",
- url: "#",
- },
- ],
- },
- ],
- navSecondary: [
- {
- title: "Settings",
- url: "#",
- icon: IconSettings,
- },
- {
- title: "Get Help",
- url: "#",
- icon: IconHelp,
- },
- {
- title: "Search",
- url: "#",
- icon: IconSearch,
- },
- ],
- documents: [
- {
- name: "Data Library",
- url: "#",
- icon: IconDatabase,
- },
- {
- name: "Reports",
- url: "#",
- icon: IconReport,
- },
- {
- name: "Word Assistant",
- url: "#",
- icon: IconFileWord,
- },
- ],
-}
-
-export function AppSidebar({ ...props }: React.ComponentProps) {
- return (
-
-
-
-
-
-
-
- Acme Inc.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/components/chart-area-interactive.tsx b/components/chart-area-interactive.tsx
deleted file mode 100644
index 4753f83..0000000
--- a/components/chart-area-interactive.tsx
+++ /dev/null
@@ -1,292 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
-
-import { useIsMobile } from "@/hooks/use-mobile"
-import {
- Card,
- CardAction,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card"
-import {
- ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@/components/ui/chart"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import {
- ToggleGroup,
- ToggleGroupItem,
-} from "@/components/ui/toggle-group"
-
-export const description = "An interactive area chart"
-
-const chartData = [
- { date: "2024-04-01", desktop: 222, mobile: 150 },
- { date: "2024-04-02", desktop: 97, mobile: 180 },
- { date: "2024-04-03", desktop: 167, mobile: 120 },
- { date: "2024-04-04", desktop: 242, mobile: 260 },
- { date: "2024-04-05", desktop: 373, mobile: 290 },
- { date: "2024-04-06", desktop: 301, mobile: 340 },
- { date: "2024-04-07", desktop: 245, mobile: 180 },
- { date: "2024-04-08", desktop: 409, mobile: 320 },
- { date: "2024-04-09", desktop: 59, mobile: 110 },
- { date: "2024-04-10", desktop: 261, mobile: 190 },
- { date: "2024-04-11", desktop: 327, mobile: 350 },
- { date: "2024-04-12", desktop: 292, mobile: 210 },
- { date: "2024-04-13", desktop: 342, mobile: 380 },
- { date: "2024-04-14", desktop: 137, mobile: 220 },
- { date: "2024-04-15", desktop: 120, mobile: 170 },
- { date: "2024-04-16", desktop: 138, mobile: 190 },
- { date: "2024-04-17", desktop: 446, mobile: 360 },
- { date: "2024-04-18", desktop: 364, mobile: 410 },
- { date: "2024-04-19", desktop: 243, mobile: 180 },
- { date: "2024-04-20", desktop: 89, mobile: 150 },
- { date: "2024-04-21", desktop: 137, mobile: 200 },
- { date: "2024-04-22", desktop: 224, mobile: 170 },
- { date: "2024-04-23", desktop: 138, mobile: 230 },
- { date: "2024-04-24", desktop: 387, mobile: 290 },
- { date: "2024-04-25", desktop: 215, mobile: 250 },
- { date: "2024-04-26", desktop: 75, mobile: 130 },
- { date: "2024-04-27", desktop: 383, mobile: 420 },
- { date: "2024-04-28", desktop: 122, mobile: 180 },
- { date: "2024-04-29", desktop: 315, mobile: 240 },
- { date: "2024-04-30", desktop: 454, mobile: 380 },
- { date: "2024-05-01", desktop: 165, mobile: 220 },
- { date: "2024-05-02", desktop: 293, mobile: 310 },
- { date: "2024-05-03", desktop: 247, mobile: 190 },
- { date: "2024-05-04", desktop: 385, mobile: 420 },
- { date: "2024-05-05", desktop: 481, mobile: 390 },
- { date: "2024-05-06", desktop: 498, mobile: 520 },
- { date: "2024-05-07", desktop: 388, mobile: 300 },
- { date: "2024-05-08", desktop: 149, mobile: 210 },
- { date: "2024-05-09", desktop: 227, mobile: 180 },
- { date: "2024-05-10", desktop: 293, mobile: 330 },
- { date: "2024-05-11", desktop: 335, mobile: 270 },
- { date: "2024-05-12", desktop: 197, mobile: 240 },
- { date: "2024-05-13", desktop: 197, mobile: 160 },
- { date: "2024-05-14", desktop: 448, mobile: 490 },
- { date: "2024-05-15", desktop: 473, mobile: 380 },
- { date: "2024-05-16", desktop: 338, mobile: 400 },
- { date: "2024-05-17", desktop: 499, mobile: 420 },
- { date: "2024-05-18", desktop: 315, mobile: 350 },
- { date: "2024-05-19", desktop: 235, mobile: 180 },
- { date: "2024-05-20", desktop: 177, mobile: 230 },
- { date: "2024-05-21", desktop: 82, mobile: 140 },
- { date: "2024-05-22", desktop: 81, mobile: 120 },
- { date: "2024-05-23", desktop: 252, mobile: 290 },
- { date: "2024-05-24", desktop: 294, mobile: 220 },
- { date: "2024-05-25", desktop: 201, mobile: 250 },
- { date: "2024-05-26", desktop: 213, mobile: 170 },
- { date: "2024-05-27", desktop: 420, mobile: 460 },
- { date: "2024-05-28", desktop: 233, mobile: 190 },
- { date: "2024-05-29", desktop: 78, mobile: 130 },
- { date: "2024-05-30", desktop: 340, mobile: 280 },
- { date: "2024-05-31", desktop: 178, mobile: 230 },
- { date: "2024-06-01", desktop: 178, mobile: 200 },
- { date: "2024-06-02", desktop: 470, mobile: 410 },
- { date: "2024-06-03", desktop: 103, mobile: 160 },
- { date: "2024-06-04", desktop: 439, mobile: 380 },
- { date: "2024-06-05", desktop: 88, mobile: 140 },
- { date: "2024-06-06", desktop: 294, mobile: 250 },
- { date: "2024-06-07", desktop: 323, mobile: 370 },
- { date: "2024-06-08", desktop: 385, mobile: 320 },
- { date: "2024-06-09", desktop: 438, mobile: 480 },
- { date: "2024-06-10", desktop: 155, mobile: 200 },
- { date: "2024-06-11", desktop: 92, mobile: 150 },
- { date: "2024-06-12", desktop: 492, mobile: 420 },
- { date: "2024-06-13", desktop: 81, mobile: 130 },
- { date: "2024-06-14", desktop: 426, mobile: 380 },
- { date: "2024-06-15", desktop: 307, mobile: 350 },
- { date: "2024-06-16", desktop: 371, mobile: 310 },
- { date: "2024-06-17", desktop: 475, mobile: 520 },
- { date: "2024-06-18", desktop: 107, mobile: 170 },
- { date: "2024-06-19", desktop: 341, mobile: 290 },
- { date: "2024-06-20", desktop: 408, mobile: 450 },
- { date: "2024-06-21", desktop: 169, mobile: 210 },
- { date: "2024-06-22", desktop: 317, mobile: 270 },
- { date: "2024-06-23", desktop: 480, mobile: 530 },
- { date: "2024-06-24", desktop: 132, mobile: 180 },
- { date: "2024-06-25", desktop: 141, mobile: 190 },
- { date: "2024-06-26", desktop: 434, mobile: 380 },
- { date: "2024-06-27", desktop: 448, mobile: 490 },
- { date: "2024-06-28", desktop: 149, mobile: 200 },
- { date: "2024-06-29", desktop: 103, mobile: 160 },
- { date: "2024-06-30", desktop: 446, mobile: 400 },
-]
-
-const chartConfig = {
- visitors: {
- label: "Visitors",
- },
- desktop: {
- label: "Desktop",
- color: "var(--primary)",
- },
- mobile: {
- label: "Mobile",
- color: "var(--primary)",
- },
-} satisfies ChartConfig
-
-export function ChartAreaInteractive() {
- const isMobile = useIsMobile()
- const [timeRange, setTimeRange] = React.useState("90d")
-
- React.useEffect(() => {
- if (isMobile) {
- setTimeRange("7d")
- }
- }, [isMobile])
-
- const filteredData = chartData.filter((item) => {
- const date = new Date(item.date)
- const referenceDate = new Date("2024-06-30")
- let daysToSubtract = 90
- if (timeRange === "30d") {
- daysToSubtract = 30
- } else if (timeRange === "7d") {
- daysToSubtract = 7
- }
- const startDate = new Date(referenceDate)
- startDate.setDate(startDate.getDate() - daysToSubtract)
- return date >= startDate
- })
-
- return (
-
-
- Total Visitors
-
-
- Total for the last 3 months
-
- Last 3 months
-
-
-
- Last 3 months
- Last 30 days
- Last 7 days
-
-
-
-
-
-
-
- Last 3 months
-
-
- Last 30 days
-
-
- Last 7 days
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- const date = new Date(value)
- return date.toLocaleDateString("en-US", {
- month: "short",
- day: "numeric",
- })
- }}
- />
- {
- return new Date(value).toLocaleDateString("en-US", {
- month: "short",
- day: "numeric",
- })
- }}
- indicator="dot"
- />
- }
- />
-
-
-
-
-
-
- )
-}
diff --git a/components/data-table.tsx b/components/data-table.tsx
deleted file mode 100644
index f55cb49..0000000
--- a/components/data-table.tsx
+++ /dev/null
@@ -1,807 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {
- DndContext,
- KeyboardSensor,
- MouseSensor,
- TouchSensor,
- closestCenter,
- useSensor,
- useSensors,
- type DragEndEvent,
- type UniqueIdentifier,
-} from "@dnd-kit/core"
-import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
-import {
- SortableContext,
- arrayMove,
- useSortable,
- verticalListSortingStrategy,
-} from "@dnd-kit/sortable"
-import { CSS } from "@dnd-kit/utilities"
-import {
- IconChevronDown,
- IconChevronLeft,
- IconChevronRight,
- IconChevronsLeft,
- IconChevronsRight,
- IconCircleCheckFilled,
- IconDotsVertical,
- IconGripVertical,
- IconLayoutColumns,
- IconLoader,
- IconPlus,
- IconTrendingUp,
-} from "@tabler/icons-react"
-import {
- ColumnDef,
- ColumnFiltersState,
- Row,
- SortingState,
- VisibilityState,
- flexRender,
- getCoreRowModel,
- getFacetedRowModel,
- getFacetedUniqueValues,
- getFilteredRowModel,
- getPaginationRowModel,
- getSortedRowModel,
- useReactTable,
-} from "@tanstack/react-table"
-import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
-import { toast } from "sonner"
-import { z } from "zod"
-
-import { useIsMobile } from "@/hooks/use-mobile"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import {
- ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@/components/ui/chart"
-import { Checkbox } from "@/components/ui/checkbox"
-import {
- Drawer,
- DrawerClose,
- DrawerContent,
- DrawerDescription,
- DrawerFooter,
- DrawerHeader,
- DrawerTitle,
- DrawerTrigger,
-} from "@/components/ui/drawer"
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-import { Separator } from "@/components/ui/separator"
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table"
-import {
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
-} from "@/components/ui/tabs"
-
-export const schema = z.object({
- id: z.number(),
- header: z.string(),
- type: z.string(),
- status: z.string(),
- target: z.string(),
- limit: z.string(),
- reviewer: z.string(),
-})
-
-// Create a separate component for the drag handle
-function DragHandle({ id }: { id: number }) {
- const { attributes, listeners } = useSortable({
- id,
- })
-
- return (
-
-
- Drag to reorder
-
- )
-}
-
-const columns: ColumnDef>[] = [
- {
- id: "drag",
- header: () => null,
- cell: ({ row }) => ,
- },
- {
- id: "select",
- header: ({ table }) => (
-
- table.toggleAllPageRowsSelected(!!value)}
- aria-label="Select all"
- />
-
- ),
- cell: ({ row }) => (
-
- row.toggleSelected(!!value)}
- aria-label="Select row"
- />
-
- ),
- enableSorting: false,
- enableHiding: false,
- },
- {
- accessorKey: "header",
- header: "Header",
- cell: ({ row }) => {
- return
- },
- enableHiding: false,
- },
- {
- accessorKey: "type",
- header: "Section Type",
- cell: ({ row }) => (
-
-
- {row.original.type}
-
-
- ),
- },
- {
- accessorKey: "status",
- header: "Status",
- cell: ({ row }) => (
-
- {row.original.status === "Done" ? (
-
- ) : (
-
- )}
- {row.original.status}
-
- ),
- },
- {
- accessorKey: "target",
- header: () => Target
,
- cell: ({ row }) => (
-
- ),
- },
- {
- accessorKey: "limit",
- header: () => Limit
,
- cell: ({ row }) => (
-
- ),
- },
- {
- accessorKey: "reviewer",
- header: "Reviewer",
- cell: ({ row }) => {
- const isAssigned = row.original.reviewer !== "Assign reviewer"
-
- if (isAssigned) {
- return row.original.reviewer
- }
-
- return (
- <>
-
- Reviewer
-
-
-
-
-
-
- Eddie Lake
-
- Jamik Tashpulatov
-
-
-
- >
- )
- },
- },
- {
- id: "actions",
- cell: () => (
-
-
-
-
- Open menu
-
-
-
- Edit
- Make a copy
- Favorite
-
- Delete
-
-
- ),
- },
-]
-
-function DraggableRow({ row }: { row: Row> }) {
- const { transform, transition, setNodeRef, isDragging } = useSortable({
- id: row.original.id,
- })
-
- return (
-
- {row.getVisibleCells().map((cell) => (
-
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- ))}
-
- )
-}
-
-export function DataTable({
- data: initialData,
-}: {
- data: z.infer[]
-}) {
- const [data, setData] = React.useState(() => initialData)
- const [rowSelection, setRowSelection] = React.useState({})
- const [columnVisibility, setColumnVisibility] =
- React.useState({})
- const [columnFilters, setColumnFilters] = React.useState(
- []
- )
- const [sorting, setSorting] = React.useState([])
- const [pagination, setPagination] = React.useState({
- pageIndex: 0,
- pageSize: 10,
- })
- const sortableId = React.useId()
- const sensors = useSensors(
- useSensor(MouseSensor, {}),
- useSensor(TouchSensor, {}),
- useSensor(KeyboardSensor, {})
- )
-
- const dataIds = React.useMemo(
- () => data?.map(({ id }) => id) || [],
- [data]
- )
-
- const table = useReactTable({
- data,
- columns,
- state: {
- sorting,
- columnVisibility,
- rowSelection,
- columnFilters,
- pagination,
- },
- getRowId: (row) => row.id.toString(),
- enableRowSelection: true,
- onRowSelectionChange: setRowSelection,
- onSortingChange: setSorting,
- onColumnFiltersChange: setColumnFilters,
- onColumnVisibilityChange: setColumnVisibility,
- onPaginationChange: setPagination,
- getCoreRowModel: getCoreRowModel(),
- getFilteredRowModel: getFilteredRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
- getSortedRowModel: getSortedRowModel(),
- getFacetedRowModel: getFacetedRowModel(),
- getFacetedUniqueValues: getFacetedUniqueValues(),
- })
-
- function handleDragEnd(event: DragEndEvent) {
- const { active, over } = event
- if (active && over && active.id !== over.id) {
- setData((data) => {
- const oldIndex = dataIds.indexOf(active.id)
- const newIndex = dataIds.indexOf(over.id)
- return arrayMove(data, oldIndex, newIndex)
- })
- }
- }
-
- return (
-
-
-
- View
-
-
-
-
-
-
- Outline
- Past Performance
- Key Personnel
- Focus Documents
-
-
-
- Outline
-
- Past Performance 3
-
-
- Key Personnel 2
-
- Focus Documents
-
-
-
-
-
-
- Customize Columns
- Columns
-
-
-
-
- {table
- .getAllColumns()
- .filter(
- (column) =>
- typeof column.accessorFn !== "undefined" &&
- column.getCanHide()
- )
- .map((column) => {
- return (
-
- column.toggleVisibility(!!value)
- }
- >
- {column.id}
-
- )
- })}
-
-
-
-
- Add Section
-
-
-
-
-
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => {
- return (
-
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext()
- )}
-
- )
- })}
-
- ))}
-
-
- {table.getRowModel().rows?.length ? (
-
- {table.getRowModel().rows.map((row) => (
-
- ))}
-
- ) : (
-
-
- No results.
-
-
- )}
-
-
-
-
-
-
- {table.getFilteredSelectedRowModel().rows.length} of{" "}
- {table.getFilteredRowModel().rows.length} row(s) selected.
-
-
-
-
- Rows per page
-
- {
- table.setPageSize(Number(value))
- }}
- >
-
-
-
-
- {[10, 20, 30, 40, 50].map((pageSize) => (
-
- {pageSize}
-
- ))}
-
-
-
-
- Page {table.getState().pagination.pageIndex + 1} of{" "}
- {table.getPageCount()}
-
-
- table.setPageIndex(0)}
- disabled={!table.getCanPreviousPage()}
- >
- Go to first page
-
-
- table.previousPage()}
- disabled={!table.getCanPreviousPage()}
- >
- Go to previous page
-
-
- table.nextPage()}
- disabled={!table.getCanNextPage()}
- >
- Go to next page
-
-
- table.setPageIndex(table.getPageCount() - 1)}
- disabled={!table.getCanNextPage()}
- >
- Go to last page
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-const chartData = [
- { month: "January", desktop: 186, mobile: 80 },
- { month: "February", desktop: 305, mobile: 200 },
- { month: "March", desktop: 237, mobile: 120 },
- { month: "April", desktop: 73, mobile: 190 },
- { month: "May", desktop: 209, mobile: 130 },
- { month: "June", desktop: 214, mobile: 140 },
-]
-
-const chartConfig = {
- desktop: {
- label: "Desktop",
- color: "var(--primary)",
- },
- mobile: {
- label: "Mobile",
- color: "var(--primary)",
- },
-} satisfies ChartConfig
-
-function TableCellViewer({ item }: { item: z.infer }) {
- const isMobile = useIsMobile()
-
- return (
-
-
-
- {item.header}
-
-
-
-
- {item.header}
-
- Showing total visitors for the last 6 months
-
-
-
- {!isMobile && (
- <>
-
-
-
- value.slice(0, 3)}
- hide
- />
- }
- />
-
-
-
-
-
-
-
- Trending up by 5.2% this month{" "}
-
-
-
- Showing total visitors for the last 6 months. This is just
- some random text to test the layout. It spans multiple lines
- and should wrap around.
-
-
-
- >
- )}
-
-
-
- Submit
-
- Done
-
-
-
-
- )
-}
diff --git a/components/file-area.tsx b/components/file-area.tsx
deleted file mode 100644
index 1e383ed..0000000
--- a/components/file-area.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-'use client'
-
-import React, { useEffect, useCallback } from 'react'
-import { readDir, writeTextFile } from '@tauri-apps/plugin-fs'
-import { join } from '@tauri-apps/api/path'
-import { Button } from './ui/button'
-import { Card, CardContent, CardHeader, CardTitle } from './ui/card'
-import { ScrollArea } from './ui/scroll-area'
-import { Badge } from './ui/badge'
-import { FolderOpen, File as FileIcon, Plus, RefreshCw } from 'lucide-react'
-import { cn } from '@/lib/utils'
-import { useI18n } from '@/hooks/useI18n'
-import { useWorkspaceStore } from '@/lib/stores'
-import { selectFileCount, selectActiveFileName, selectFolderName } from '@/lib/stores/workspace-store'
-import { UXFeedback } from '@/lib/ux-feedback'
-
-export function FileArea({ className }: { className?: string }) {
- const { t } = useI18n()
-
- const workspaceState = useWorkspaceStore()
-
- const fileCount = selectFileCount(workspaceState)
- const activeFileName = selectActiveFileName(workspaceState)
- const folderName = selectFolderName(workspaceState)
-
- const {
- folderPath,
- files,
- isLoadingFiles,
- fileError,
- setFiles,
- setSelectedFile,
- setIsLoadingFiles,
- setFileError,
- } = workspaceState
-
- const loadFiles = useCallback(async () => {
- if (!folderPath) return
-
- setIsLoadingFiles(true)
- setFileError(null)
-
- try {
- const entries = await readDir(folderPath)
- const fileEntries = entries.map((entry) => ({
- name: entry.name,
- isFile: entry.isFile,
- }))
-
- fileEntries.sort((a, b) => {
- if (a.isFile !== b.isFile) {
- return a.isFile ? 1 : -1
- }
- return a.name.localeCompare(b.name)
- })
-
- setFiles(fileEntries)
- } catch (error) {
- UXFeedback.handleError(error, 'Failed to load files')
- }
- }, [folderPath, setFiles, setIsLoadingFiles, setFileError])
-
- useEffect(() => {
- loadFiles()
- }, [folderPath, loadFiles])
-
- const handleFileClick = useCallback(
- async (fileName: string, isFile: boolean) => {
- if (folderPath && isFile) {
- const filePath = await join(folderPath, fileName)
- setSelectedFile(filePath)
- }
- },
- [folderPath, setSelectedFile],
- )
-
- const handleCreateFile = useCallback(async () => {
- if (!folderPath) return
-
- try {
- const fileName = await UXFeedback.showSaveDialog({
- filters: [
- {
- name: 'Markdown',
- extensions: ['md'],
- },
- ],
- })
-
- if (!fileName) return
-
- const finalFileName = fileName.endsWith('.md') ? fileName : `${fileName}.md`
-
- const filePath = await join(folderPath, finalFileName)
-
- await writeTextFile(
- filePath,
- '# ' + finalFileName.replace('.md', '') + '\n\n' + (t('file.newFileContent') || 'Start writing here...') + '\n'
- )
-
- await loadFiles()
- setSelectedFile(filePath)
- UXFeedback.success(t('file.created') || `Created ${finalFileName}`)
- } catch (error) {
- UXFeedback.handleError(error, 'Failed to create file')
- }
- }, [folderPath, loadFiles, setSelectedFile, t])
-
- const getFileIcon = (fileName: string, isFile: boolean) => {
- if (!isFile) {
- return (
-
-
-
- )
- }
-
- if (fileName.endsWith('.md')) {
- return
- }
-
- return
- }
-
- return (
-
-
-
-
-
- {t('file.folderLabel') || 'FOLDER'}
-
-
- {folderName || (t('file.noFolder') || 'No folder selected')}
-
-
-
- {folderPath && (
-
- {t('file.filesCount', { count: fileCount }) || `${fileCount} files`}
-
- )}
- {folderPath && (
-
-
- {t('file.newFile') || 'New'}
-
- )}
-
-
-
-
-
- {isLoadingFiles ? (
-
-
-
- {t('file.loading') || 'Loading files...'}
-
-
- ) : fileError ? (
-
-
-
- {fileError}
-
-
- {t('file.retry') || 'Retry'}
-
-
- ) : (
-
-
- {folderPath ? (
-
- {files.map((entry, index) => {
- const isEntryActive = entry.isFile && entry.name === activeFileName
- return (
-
handleFileClick(entry.name, entry.isFile)}
- >
- {getFileIcon(entry.name, entry.isFile)}
- {entry.name}
-
- )
- })}
- {files.length === 0 && (
-
-
-
-
- {t('file.emptyFolder') || 'Empty folder'}
-
-
-
- )}
-
- ) : (
-
-
-
- {t('file.chooseFolder') || 'Choose a folder'}
-
-
- )}
-
-
- )}
-
-
- )
-}
diff --git a/components/i18n-provider.tsx b/components/i18n-provider.tsx
deleted file mode 100644
index db5fb0b..0000000
--- a/components/i18n-provider.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-"use client"
-
-import React, { useCallback, useEffect, useMemo, useState } from "react"
-import {
- detectLocale,
- formatMessage,
- getMessage,
- persistLocale,
- type Locale,
-} from "@/lib/i18n"
-
-type I18nContextValue = {
- locale: Locale
- setLocale: (locale: Locale) => void
- t: (key: string, params?: Record) => string
-}
-
-export const I18nContext = React.createContext(null)
-
-export function I18nProvider({ children }: { children: React.ReactNode }) {
- const [locale, setLocaleState] = useState("en")
-
- const setLocale = useCallback((next: Locale) => {
- setLocaleState(next)
- persistLocale(next)
- }, [])
-
- const t = useCallback(
- (key: string, params?: Record) => {
- const message = getMessage(locale, key)
- return formatMessage(message, params)
- },
- [locale]
- )
-
- useEffect(() => {
- setLocaleState(detectLocale())
- }, [])
-
- useEffect(() => {
- document.documentElement.lang = locale
- }, [locale])
-
- const value = useMemo(
- () => ({
- locale,
- setLocale,
- t,
- }),
- [locale, setLocale, t]
- )
-
- return {children}
-}
diff --git a/components/language-toggle.tsx b/components/language-toggle.tsx
deleted file mode 100644
index 0319866..0000000
--- a/components/language-toggle.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-"use client"
-
-import { useI18n } from "@/hooks/useI18n"
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select"
-
-export function LanguageToggle() {
- const { locale, setLocale, t } = useI18n()
-
- return (
- setLocale(value as typeof locale)}>
-
-
-
-
- 中文
- English
-
-
- )
-}
diff --git a/components/login-form.tsx b/components/login-form.tsx
deleted file mode 100644
index cec1031..0000000
--- a/components/login-form.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-'use client'
-
-import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
-import {
- Card,
- CardContent,
- CardDescription,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { useI18n } from "@/hooks/useI18n"
-
-export function LoginForm({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- const { t } = useI18n()
-
- return (
-
-
-
- {t('login.title')}
-
- {t('login.subtitle')}
-
-
-
-
-
-
-
-
- )
-}
diff --git a/components/markdown-editor.tsx b/components/markdown-editor.tsx
deleted file mode 100644
index 4f889dd..0000000
--- a/components/markdown-editor.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-'use client'
-
-import React, { useState } from 'react'
-import {
- MDXEditor,
- type MDXEditorMethods,
- type MDXEditorProps,
- headingsPlugin,
- listsPlugin,
- quotePlugin,
- thematicBreakPlugin,
- toolbarPlugin,
- BoldItalicUnderlineToggles,
- imagePlugin,
- linkPlugin,
- linkDialogPlugin,
- tablePlugin,
- InsertTable,
- BlockTypeSelect,
- CreateLink,
- UndoRedo,
- codeBlockPlugin,
- codeMirrorPlugin,
- InsertCodeBlock,
-} from '@mdxeditor/editor'
-import '@mdxeditor/editor/style.css'
-import { join } from '@tauri-apps/api/path'
-import { writeFile } from '@tauri-apps/plugin-fs'
-import { Separator } from './ui/separator'
-import { useS3Config } from '@/hooks/useS3Config'
-import { toast } from 'sonner'
-
-interface MarkdownEditorProps extends MDXEditorProps {
- editorRef?: React.ForwardedRef
- folderPath: string | null
-}
-
-const MarkdownEditor: React.FC = ({
- editorRef,
- folderPath,
- ...props
-}) => {
- const { hasConfig, uploadImage } = useS3Config()
- const [uploading, setUploading] = useState(false)
-
- const imageUploadHandler = async (image: File) => {
- setUploading(true)
- try {
- if (hasConfig()) {
- const imageName = `${Date.now()}-${image.name}`
- const fileData = await image.arrayBuffer()
- const url = await uploadImage(imageName, fileData)
- toast.success('Image uploaded to S3 successfully')
- return url
- } else if (folderPath) {
- const imageName = `${Date.now()}-${image.name}`
- const imagePath = await join(folderPath, 'assets', imageName)
-
- const reader = new FileReader()
- reader.readAsArrayBuffer(image)
- await new Promise((resolve, reject) => {
- reader.onload = async () => {
- try {
- await writeFile(imagePath, new Uint8Array(reader.result as ArrayBuffer))
- resolve(undefined)
- } catch (e) {
- reject(e)
- }
- }
- reader.onerror = reject
- })
-
- return imagePath
- } else {
- toast.error('No S3 configuration or folder path available')
- return ''
- }
- } catch (error) {
- toast.error(`Failed to upload image: ${error}`)
- return ''
- } finally {
- setUploading(false)
- }
- }
-
- if (uploading) {
- return (
-
- )
- }
-
- return (
-
-
(
-
-
-
-
-
-
-
-
-
-
-
-
- ),
- }),
- headingsPlugin(),
- listsPlugin(),
- quotePlugin(),
- thematicBreakPlugin(),
- linkPlugin(),
- linkDialogPlugin(),
- tablePlugin(),
- codeBlockPlugin({ defaultCodeBlockLanguage: 'js' }),
- codeMirrorPlugin({
- codeBlockLanguages: {
- js: 'JavaScript',
- ts: 'TypeScript',
- tsx: 'TypeScript React',
- css: 'CSS',
- html: 'HTML',
- python: 'Python',
- rust: 'Rust',
- go: 'Go',
- java: 'Java',
- c: 'C',
- cpp: 'C++',
- sh: 'Shell',
- json: 'JSON',
- yaml: 'YAML',
- xml: 'XML',
- sql: 'SQL',
- md: 'Markdown',
- }
- }),
- imagePlugin({ imageUploadHandler }),
- ]}
- contentEditableClassName="markdown-editor max-w-none p-6 min-h-screen focus:outline-none bg-background text-foreground"
- {...props}
- ref={editorRef}
- className="h-full overflow-auto"
- />
-
- )
-}
-
-export default MarkdownEditor
diff --git a/components/nav-documents.tsx b/components/nav-documents.tsx
deleted file mode 100644
index 66f6ee7..0000000
--- a/components/nav-documents.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-"use client"
-
-import {
- IconDots,
- IconFolder,
- IconShare3,
- IconTrash,
- type Icon,
-} from "@tabler/icons-react"
-
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@/components/ui/sidebar"
-
-export function NavDocuments({
- items = [],
-}: {
- items?: {
- name: string
- url: string
- icon: Icon
- }[]
-}) {
- const { isMobile } = useSidebar()
-
- return (
-
- Documents
-
- {items.map((item) => (
-
-
-
-
- {item.name}
-
-
-
-
-
-
- More
-
-
-
-
-
- Open
-
-
-
- Share
-
-
-
-
- Delete
-
-
-
-
- ))}
-
-
-
- More
-
-
-
-
- )
-}
diff --git a/components/nav-main.tsx b/components/nav-main.tsx
deleted file mode 100644
index 694c230..0000000
--- a/components/nav-main.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-"use client"
-
-import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
-
-import { Button } from "@/components/ui/button"
-import {
- SidebarGroup,
- SidebarGroupContent,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
-
-export function NavMain({
- items,
-}: {
- items: {
- title: string
- url: string
- icon?: Icon
- }[]
-}) {
- return (
-
-
-
-
-
-
- Quick Create
-
-
-
- Inbox
-
-
-
-
- {items.map((item) => (
-
-
- {item.icon && }
- {item.title}
-
-
- ))}
-
-
-
- )
-}
diff --git a/components/nav-secondary.tsx b/components/nav-secondary.tsx
deleted file mode 100644
index 3f3636f..0000000
--- a/components/nav-secondary.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { type Icon } from "@tabler/icons-react"
-
-import {
- SidebarGroup,
- SidebarGroupContent,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
-} from "@/components/ui/sidebar"
-
-export function NavSecondary({
- items,
- ...props
-}: {
- items: {
- title: string
- url: string
- icon: Icon
- }[]
-} & React.ComponentPropsWithoutRef) {
- return (
-
-
-
- {items.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
-
-
-
- )
-}
diff --git a/components/nav-user.tsx b/components/nav-user.tsx
deleted file mode 100644
index 7c49dc7..0000000
--- a/components/nav-user.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client"
-
-import {
- IconCreditCard,
- IconDotsVertical,
- IconLogout,
- IconNotification,
- IconUserCircle,
-} from "@tabler/icons-react"
-
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@/components/ui/avatar"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@/components/ui/sidebar"
-
-export function NavUser({
- user,
-}: {
- user: {
- name: string
- email: string
- avatar: string
- }
-}) {
- const { isMobile } = useSidebar()
-
- return (
-
-
-
-
-
-
-
- CN
-
-
- {user.name}
-
- {user.email}
-
-
-
-
-
-
-
-
-
-
- CN
-
-
- {user.name}
-
- {user.email}
-
-
-
-
-
-
-
-
- Account
-
-
-
- Billing
-
-
-
- Notifications
-
-
-
-
-
- Log out
-
-
-
-
-
- )
-}
diff --git a/components/s3-config-dialog.tsx b/components/s3-config-dialog.tsx
deleted file mode 100644
index 16eb8a5..0000000
--- a/components/s3-config-dialog.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-'use client'
-
-import React, { useState, useEffect } from 'react'
-import { invoke } from '@tauri-apps/api/core'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from './ui/dialog'
-import { Button } from './ui/button'
-import { Input } from './ui/input'
-import { Label } from './ui/label'
-import { toast } from 'sonner'
-
-interface S3Config {
- bucket_name: string
- region: string
- access_key_id: string
- secret_access_key: string
- endpoint_url?: string
- path_prefix?: string
-}
-
-interface S3ConfigDialogProps {
- trigger?: React.ReactNode
- onConfigSaved?: () => void
-}
-
-export function S3ConfigDialog({ trigger, onConfigSaved }: S3ConfigDialogProps) {
- const [open, setOpen] = useState(false)
- const [loading, setLoading] = useState(false)
- const [hasConfig, setHasConfig] = useState(false)
- const [formData, setFormData] = useState({
- bucket_name: '',
- region: 'us-east-1',
- access_key_id: '',
- secret_access_key: '',
- endpoint_url: '',
- path_prefix: '',
- })
-
- const loadConfig = async () => {
- try {
- const config = await invoke('load_s3_config')
- if (config) {
- setFormData(config)
- setHasConfig(true)
- } else {
- setHasConfig(false)
- }
- } catch (error) {
- console.error('Failed to load S3 config:', error)
- }
- }
-
- useEffect(() => {
- if (open) {
- loadConfig()
- }
- }, [open])
-
- const handleSave = async () => {
- if (!formData.bucket_name || !formData.region || !formData.access_key_id || !formData.secret_access_key) {
- toast.error('Please fill in all required fields')
- return
- }
-
- setLoading(true)
- try {
- await invoke('save_s3_config', { config: formData })
- toast.success('S3 configuration saved successfully')
- setHasConfig(true)
- onConfigSaved?.()
- setOpen(false)
- } catch (error) {
- toast.error(`Failed to save S3 config: ${error}`)
- } finally {
- setLoading(false)
- }
- }
-
- const handleDelete = async () => {
- if (!confirm('Are you sure you want to delete the S3 configuration?')) {
- return
- }
-
- setLoading(true)
- try {
- await invoke('delete_s3_config')
- toast.success('S3 configuration deleted')
- setHasConfig(false)
- setFormData({
- bucket_name: '',
- region: 'us-east-1',
- access_key_id: '',
- secret_access_key: '',
- endpoint_url: '',
- path_prefix: '',
- })
- onConfigSaved?.()
- } catch (error) {
- toast.error(`Failed to delete S3 config: ${error}`)
- } finally {
- setLoading(false)
- }
- }
-
- const handleInputChange = (field: keyof S3Config, value: string) => {
- setFormData((prev) => ({ ...prev, [field]: value }))
- }
-
- return (
-
- {trigger && {trigger} }
-
-
- S3 Configuration
-
- Configure your S3-compatible storage for automatic image uploads.
-
-
-
-
-
- Bucket Name *
- handleInputChange('bucket_name', e.target.value)}
- placeholder="my-bucket"
- />
-
-
-
- Region *
- handleInputChange('region', e.target.value)}
- placeholder="us-east-1"
- />
-
-
-
- Access Key ID *
- handleInputChange('access_key_id', e.target.value)}
- placeholder="AKIAIOSFODNN7EXAMPLE"
- type="password"
- />
-
-
-
- Secret Access Key *
- handleInputChange('secret_access_key', e.target.value)}
- placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
- type="password"
- />
-
-
-
-
- Endpoint URL (Optional)
-
-
handleInputChange('endpoint_url', e.target.value)}
- placeholder="https://s3.amazonaws.com"
- />
-
- Leave empty for AWS S3. Use this for S3-compatible services like MinIO, Wasabi, etc.
-
-
-
-
-
- Path Prefix (Optional)
-
-
handleInputChange('path_prefix', e.target.value)}
- placeholder="images/blog"
- />
-
- Uploads will be stored under this prefix in the bucket
-
-
-
-
-
- {hasConfig && (
-
- Delete Configuration
-
- )}
-
- setOpen(false)} disabled={loading}>
- Cancel
-
-
- {loading ? 'Saving...' : 'Save Configuration'}
-
-
-
-
-
- )
-}
diff --git a/components/section-cards.tsx b/components/section-cards.tsx
deleted file mode 100644
index bb2e93f..0000000
--- a/components/section-cards.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
-
-import { Badge } from "@/components/ui/badge"
-import {
- Card,
- CardAction,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card"
-
-export function SectionCards() {
- return (
-
-
-
- Total Revenue
-
- $1,250.00
-
-
-
-
- +12.5%
-
-
-
-
-
- Trending up this month
-
-
- Visitors for the last 6 months
-
-
-
-
-
- New Customers
-
- 1,234
-
-
-
-
- -20%
-
-
-
-
-
- Down 20% this period
-
-
- Acquisition needs attention
-
-
-
-
-
- Active Accounts
-
- 45,678
-
-
-
-
- +12.5%
-
-
-
-
-
- Strong user retention
-
- Engagement exceed targets
-
-
-
-
- Growth Rate
-
- 4.5%
-
-
-
-
- +4.5%
-
-
-
-
-
- Steady performance increase
-
- Meets growth projections
-
-
-
- )
-}
diff --git a/components/site-header.tsx b/components/site-header.tsx
deleted file mode 100644
index 59c4f02..0000000
--- a/components/site-header.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Button } from "@/components/ui/button"
-import { Separator } from "@/components/ui/separator"
-import { SidebarTrigger } from "@/components/ui/sidebar"
-
-export function SiteHeader() {
- return (
-
- )
-}
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
deleted file mode 100644
index ae7c9b6..0000000
--- a/components/theme-toggle.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-'use client'
-
-import { Button } from '@/components/ui/button'
-import { useEffect, useState } from 'react'
-import { useI18n } from '@/hooks/useI18n'
-
-export function ThemeToggle() {
- const { t } = useI18n()
- const [theme, setTheme] = useState<'dark' | 'light'>('light')
-
- useEffect(() => {
- // 从本地存储获取主题设置
- const savedTheme = localStorage.getItem('onlywrite-theme') as 'dark' | 'light'
- if (savedTheme) {
- setTheme(savedTheme)
- } else {
- // 检查系统主题偏好
- const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
- setTheme(systemTheme)
- }
- }, [])
-
- useEffect(() => {
- // 更新文档类
- const root = document.documentElement
- root.classList.remove('light', 'dark')
- root.classList.add(theme)
-
- // 保存到本地存储
- localStorage.setItem('onlywrite-theme', theme)
- }, [theme])
-
- const toggleTheme = () => {
- // Add a brief delay to prevent flash during theme transition
- document.body.style.transition = 'none';
- setTheme(prev => prev === 'light' ? 'dark' : 'light');
-
- // Re-enable transitions after theme change
- requestAnimationFrame(() => {
- document.body.style.transition = '';
- });
- }
-
- return (
-
-
-
-
-
-
-
-
-
- {theme === 'light' ? t('actions.switchToDark') : t('actions.switchToLight')}
-
-
- )
-}
diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx
deleted file mode 100644
index 4a8cca4..0000000
--- a/components/ui/accordion.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-"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
deleted file mode 100644
index 71ee6e1..0000000
--- a/components/ui/alert-dialog.tsx
+++ /dev/null
@@ -1,157 +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"
-
-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
deleted file mode 100644
index 7ec3faf..0000000
--- a/components/ui/alert.tsx
+++ /dev/null
@@ -1,66 +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 border border-foreground 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
deleted file mode 100644
index 3df3fd0..0000000
--- a/components/ui/aspect-ratio.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-"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
deleted file mode 100644
index 71e428b..0000000
--- a/components/ui/avatar.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"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
deleted file mode 100644
index 1d23972..0000000
--- a/components/ui/badge.tsx
+++ /dev/null
@@ -1,46 +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 badgeVariants = cva(
- "inline-flex items-center justify-center border-2 border-foreground px-2 py-0.5 text-[0.65rem] font-mono tracking-widest w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none transition-colors duration-100 overflow-hidden",
- {
- variants: {
- variant: {
- default:
- "bg-foreground text-background",
- secondary:
- "bg-background text-foreground",
- destructive:
- "bg-foreground text-background",
- outline:
- "bg-transparent text-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
deleted file mode 100644
index eb88f32..0000000
--- a/components/ui/breadcrumb.tsx
+++ /dev/null
@@ -1,109 +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"
-
-function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
- return
-}
-
-function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
- return (
-
- )
-}
-
-function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
- return (
-
- )
-}
-
-function BreadcrumbLink({
- asChild,
- className,
- ...props
-}: React.ComponentProps<"a"> & {
- asChild?: boolean
-}) {
- const Comp = asChild ? Slot : "a"
-
- return (
-
- )
-}
-
-function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
- return (
-
- )
-}
-
-function BreadcrumbSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) {
- return (
- svg]:size-3.5", className)}
- {...props}
- >
- {children ?? }
-
- )
-}
-
-function BreadcrumbEllipsis({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
-
-
- More
-
- )
-}
-
-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 ec29811..0000000
--- a/components/ui/button.tsx
+++ /dev/null
@@ -1,59 +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 border-2 border-foreground bg-foreground text-background font-mono text-xs font-medium uppercase tracking-widest transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:scale-95",
- {
- variants: {
- variant: {
- default:
- "bg-foreground text-background hover:bg-background hover:text-foreground hover:border-foreground",
- destructive:
- "bg-foreground text-background hover:bg-background hover:text-foreground hover:border-foreground",
- outline:
- "bg-transparent text-foreground hover:bg-foreground hover:text-background",
- secondary:
- "bg-background text-foreground hover:bg-foreground hover:text-background",
- ghost:
- "border-transparent bg-transparent text-foreground hover:underline",
- link: "border-transparent bg-transparent text-foreground underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-6 py-2 has-[>svg]:px-5",
- sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
- lg: "h-12 px-8 py-3 has-[>svg]:px-6",
- icon: "size-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-function Button({
- className,
- variant,
- size,
- asChild = false,
- ...props
-}: React.ComponentProps<"button"> &
- VariantProps & {
- asChild?: boolean
- }) {
- const Comp = asChild ? Slot : "button"
-
- return (
-
- )
-}
-
-export { Button, buttonVariants }
diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx
deleted file mode 100644
index b3632f1..0000000
--- a/components/ui/calendar.tsx
+++ /dev/null
@@ -1,91 +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"
-
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
-}: React.ComponentProps) {
- return (
- .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
- : "[&:has([aria-selected])]:rounded-md"
- ),
- day: cn(
- buttonVariants({ variant: "ghost" }),
- "size-8 p-0 font-normal aria-selected:opacity-100"
- ),
- day_range_start:
- "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_range_end:
- "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_selected:
- "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
- day_today: "bg-accent text-accent-foreground",
- day_outside:
- "day-outside text-muted-foreground aria-selected:text-muted-foreground",
- day_disabled: "text-muted-foreground opacity-50",
- day_range_middle:
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
- day_hidden: "invisible",
- ...classNames,
- }}
- components={{
- Nav: ({ onPreviousClick, onNextClick, previousMonth, nextMonth }) => (
-
- onPreviousClick?.(event)}
- disabled={!previousMonth}
- className={cn(buttonVariants({ variant: "ghost" }), "h-7 w-7 p-0")}
- aria-label="Go to previous month"
- >
-
-
- onNextClick?.(event)}
- disabled={!nextMonth}
- className={cn(buttonVariants({ variant: "ghost" }), "h-7 w-7 p-0")}
- aria-label="Go to next month"
- >
-
-
-
- ),
- }}
- {...props}
- />
- )
-}
-
-export { Calendar }
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
deleted file mode 100644
index 1cf502e..0000000
--- a/components/ui/card.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-function Card({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- )
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-}
diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx
deleted file mode 100644
index 0e05a77..0000000
--- a/components/ui/carousel.tsx
+++ /dev/null
@@ -1,241 +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
-}
-
-function Carousel({
- orientation = "horizontal",
- opts,
- setApi,
- plugins,
- className,
- children,
- ...props
-}: React.ComponentProps<"div"> & CarouselProps) {
- 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}
-
-
- )
-}
-
-function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
- const { carouselRef, orientation } = useCarousel()
-
- return (
-
- )
-}
-
-function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
- const { orientation } = useCarousel()
-
- return (
-
- )
-}
-
-function CarouselPrevious({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
-
- return (
-
-
- Previous slide
-
- )
-}
-
-function CarouselNext({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollNext, canScrollNext } = useCarousel()
-
- return (
-
-
- Next slide
-
- )
-}
-
-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 97cc280..0000000
--- a/components/ui/chart.tsx
+++ /dev/null
@@ -1,353 +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
-}
-
-function ChartContainer({
- id,
- className,
- children,
- config,
- ...props
-}: React.ComponentProps<"div"> & {
- config: ChartConfig
- children: React.ComponentProps<
- typeof RechartsPrimitive.ResponsiveContainer
- >["children"]
-}) {
- const uniqueId = React.useId()
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
-
- return (
-
-
-
-
- {children}
-
-
-
- )
-}
-
-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 (
-