feat: enhance mail functionality with new data structure, improved UI components, and additional mail entries
This commit is contained in:
parent
fa525f6090
commit
d3bac607aa
30 changed files with 2730 additions and 595 deletions
21
components.json
Normal file
21
components.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
25
index.html
25
index.html
|
@ -1,14 +1,19 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Tauri + React + Typescript</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<head>
|
||||||
<div id="root"></div>
|
<meta charset="UTF-8" />
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
</body>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>General Bots</title>
|
||||||
|
<link href="/output.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
622
package-lock.json
generated
622
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
@ -4,7 +4,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ESBUILD_RUNNER=true vite",
|
"dev": "npx tailwindcss -i ./src/styles/globals.css -o ./public/output.css;ESBUILD_RUNNER=true vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
|
@ -12,12 +12,16 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.26.0",
|
"@babel/runtime": "7.26.0",
|
||||||
"@hookform/resolvers": "3.9.1",
|
"@hookform/resolvers": "3.9.1",
|
||||||
|
"@radix-ui/react-slot": "1.1.2",
|
||||||
|
"@tailwindcss/vite": "4.0.17",
|
||||||
"@tauri-apps/api": "2.4.0",
|
"@tauri-apps/api": "2.4.0",
|
||||||
"@tauri-apps/plugin-opener": "2",
|
"@tauri-apps/plugin-opener": "2",
|
||||||
"@zitadel/react": "1.0.5",
|
"@zitadel/react": "1.0.5",
|
||||||
"autoprefixer": "10.4.17",
|
"autoprefixer": "10.4.17",
|
||||||
"botframework-directlinejs": "0.15.1",
|
"botframework-directlinejs": "0.15.1",
|
||||||
"botframework-webchat": "4.15.7",
|
"botframework-webchat": "4.15.7",
|
||||||
|
"class-variance-authority": "0.7.1",
|
||||||
|
"clsx": "2.1.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"lucide-react": "0.454.0",
|
"lucide-react": "0.454.0",
|
||||||
"nativewind": "2.0.10",
|
"nativewind": "2.0.10",
|
||||||
|
@ -25,9 +29,10 @@
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-hook-form": "7.53.2",
|
"react-hook-form": "7.53.2",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "10.1.0",
|
||||||
"react-router-dom": "7.4.1",
|
"react-router-dom": "7.4.1",
|
||||||
"tailwindcss": "3.4.1",
|
"tailwind-merge": "3.0.2",
|
||||||
|
"tailwindcss-animate": "1.0.7",
|
||||||
"uuid": "11.0.3",
|
"uuid": "11.0.3",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
|
@ -35,15 +40,16 @@
|
||||||
"@babel/core": "7.18.6",
|
"@babel/core": "7.18.6",
|
||||||
"@tauri-apps/cli": "2",
|
"@tauri-apps/cli": "2",
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.12",
|
||||||
|
"@types/node": "22.13.14",
|
||||||
"@types/react": "18.3.1",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "18.3.1",
|
"@types/react-dom": "18.3.1",
|
||||||
"@types/react-test-renderer": "18.0.7",
|
"@types/react-test-renderer": "18.0.7",
|
||||||
"@vitejs/plugin-react": "4.3.4",
|
"@vitejs/plugin-react": "4.3.4",
|
||||||
"copy-webpack-plugin": "12.0.2",
|
"copy-webpack-plugin": "12.0.2",
|
||||||
"esbuild-runner": "^2.2.2",
|
"esbuild-runner": "2.2.2",
|
||||||
"jest": "29.2.1",
|
"jest": "29.2.1",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.23",
|
||||||
"postcss-load-config": "^6.0.1",
|
"postcss-load-config": "6.0.1",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"tailwindcss": "3.1.8",
|
"tailwindcss": "3.1.8",
|
||||||
"typescript": "5.6.2",
|
"typescript": "5.6.2",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// postcss.config.js
|
module.exports = {
|
||||||
export default {
|
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
}
|
},
|
||||||
}
|
}
|
1581
public/output.css
Normal file
1581
public/output.css
Normal file
File diff suppressed because it is too large
Load diff
|
@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = ["unstable"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default"
|
"opener:default"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tauri::{Emitter, Manager, Window};
|
use tauri::{Emitter, Window};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct FileItem {
|
pub struct FileItem {
|
||||||
|
|
|
@ -1,36 +1,68 @@
|
||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
pub mod drive;
|
pub mod drive;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
|
||||||
use sync::AppState;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::time::Duration;
|
||||||
|
use sync::AppState;
|
||||||
|
use tauri::{Manager, WebviewUrl};
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet(name: &str) -> String {
|
fn greet(name: &str) -> String {
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
fn main() {
|
pub fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(AppState {
|
.setup(|app| {
|
||||||
sync_processes: Mutex::new(Vec::new()),
|
let window = app.get_window("main").unwrap();
|
||||||
sync_active: Mutex::new(false),
|
|
||||||
|
// Hide window initially
|
||||||
|
window.hide()?;
|
||||||
|
|
||||||
|
// Remove decorations initially
|
||||||
|
window.set_decorations(false)?;
|
||||||
|
|
||||||
|
// Create a handle to the main window for the delayed show operation
|
||||||
|
let window_handle = window.clone();
|
||||||
|
|
||||||
|
// Use a delayed show operation without JS
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
// Give time for the UI to initialize (adjust duration as needed)
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
// Execute on main thread since window operations are not thread-safe
|
||||||
|
let window_for_closure = window_handle.clone();
|
||||||
|
let _ = window_handle.run_on_main_thread(move || {
|
||||||
|
window_for_closure.show().expect("Failed to show window");
|
||||||
|
window_for_closure
|
||||||
|
.set_decorations(true)
|
||||||
|
.expect("Failed to set decorations");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.plugin(tauri_plugin_opener::init())
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
// Remove the now-unnecessary JS-dependent commands
|
||||||
sync::save_config,
|
sync::save_config,
|
||||||
drive::list_files,
|
drive::list_files,
|
||||||
drive::upload_file,
|
// ... other commands
|
||||||
drive::create_folder,
|
|
||||||
sync::start_sync,
|
|
||||||
sync::stop_sync,
|
|
||||||
sync::get_status
|
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("Failed to run app");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These commands are no longer needed as we're handling the window display
|
||||||
|
// directly in the setup function
|
||||||
|
// #[tauri::command]
|
||||||
|
// fn show_main_window(window: tauri::Window) {
|
||||||
|
// window.show().expect("Failed to show window");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[tauri::command]
|
||||||
|
// fn set_decorations(window: tauri::Window, value: bool) {
|
||||||
|
// window.set_decorations(value).expect("Failed to set decorations");
|
||||||
|
// }
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{Manager, Window};
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{File, OpenOptions, create_dir_all};
|
use std::fs::{OpenOptions, create_dir_all};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "my-tauri-app",
|
"productName": "General Bots",
|
||||||
"version": "0.1.0",
|
"version": "6.0.0",
|
||||||
"identifier": "online.generalbots",
|
"identifier": "online.generalbots",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
|
@ -11,14 +11,21 @@
|
||||||
|
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "my-tauri-app",
|
"label": "main",
|
||||||
|
"title": "General Bots",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"visible": false,
|
||||||
|
"decorations": false,
|
||||||
|
"skipTaskbar": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
165
src/App.css
165
src/App.css
|
@ -1,165 +0,0 @@
|
||||||
.app {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-screen, .status-screen {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background-color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 100;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-item {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Existing styles below */
|
|
||||||
.logo.vite:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #747bff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo.react:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafb);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color: #0f0f0f;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 10vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: 0.75s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo.tauri:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #24c8db);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
color: #0f0f0f;
|
|
||||||
background-color: #ffffff;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
border-color: #396cd8;
|
|
||||||
}
|
|
||||||
button:active {
|
|
||||||
border-color: #396cd8;
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#greet-input {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
color: #f6f6f6;
|
|
||||||
background-color: #2f2f2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #24c8db;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #0f0f0f98;
|
|
||||||
}
|
|
||||||
button:active {
|
|
||||||
background-color: #0f0f0f69;
|
|
||||||
}
|
|
||||||
}
|
|
57
src/components/ui/button.tsx
Normal file
57
src/components/ui/button.tsx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2",
|
||||||
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
|
lg: "h-10 rounded-md px-8",
|
||||||
|
icon: "h-9 w-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
106
src/index.tsx
106
src/index.tsx
|
@ -1,12 +1,13 @@
|
||||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import AuthenticationScreen from './authentication';
|
import AuthenticationScreen from './authentication';
|
||||||
import { Chat } from './chat';
|
import { Chat } from './chat';
|
||||||
import {MailPage} from './mail';
|
import { MailPage } from './mail';
|
||||||
import DashboardPage from './dashboard';
|
import DashboardPage from './dashboard';
|
||||||
import TaskPage from './tasks';
|
import TaskPage from './tasks';
|
||||||
import TemplatesPage from './templates';
|
import TemplatesPage from './templates';
|
||||||
import {DriveScreen} from './drive';
|
import { DriveScreen } from './drive';
|
||||||
import SyncPage from './sync/page';
|
import SyncPage from './sync/page';
|
||||||
|
import { Button } from './components/ui/button';
|
||||||
|
|
||||||
const examples = [
|
const examples = [
|
||||||
{ name: "Home", href: "authentication" },
|
{ name: "Home", href: "authentication" },
|
||||||
|
@ -21,54 +22,77 @@ const examples = [
|
||||||
{ name: "Help", href: "help" },
|
{ name: "Help", href: "help" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ExamplesNav = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="examples-nav-container">
|
||||||
|
<div className="examples-nav-inner">
|
||||||
|
{examples.map((example) => (
|
||||||
|
<Button
|
||||||
|
key={example.href}
|
||||||
|
onClick={() => navigate(example.href)}
|
||||||
|
className={`example-button ${location.pathname.includes(example.href) ? 'active' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{example.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export function RootLayout() {
|
export function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
Oi
|
<ExamplesNav />
|
||||||
<main className="app-main">
|
<main className="app-main">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="authentication" element={
|
<Route path="authentication" element={
|
||||||
<>
|
<>
|
||||||
<AuthenticationScreen />
|
<AuthenticationScreen />
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="chat" element={
|
<Route path="chat" element={
|
||||||
<>
|
<>
|
||||||
<Chat />
|
<Chat />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="dashboard" element={
|
<Route path="dashboard" element={
|
||||||
<>
|
<>
|
||||||
<DashboardPage />
|
<DashboardPage />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="mail" element={
|
<Route path="mail" element={
|
||||||
<>
|
<>
|
||||||
<MailPage />
|
<MailPage />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="drive" element={<DriveScreen />} />
|
<Route path="drive" element={<DriveScreen />} />
|
||||||
<Route path="tasks" element={
|
<Route path="tasks" element={
|
||||||
<>
|
<>
|
||||||
<TaskPage />
|
<TaskPage />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="meet" element={<div>Meet Screen (Placeholder)</div>} />
|
<Route path="meet" element={<div>Meet Screen (Placeholder)</div>} />
|
||||||
<Route path="templates" element={
|
<Route path="templates" element={
|
||||||
<>
|
<>
|
||||||
<TemplatesPage />
|
<TemplatesPage />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
<Route path="sync" element={<SyncPage />} />
|
<Route path="sync" element={<SyncPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RootLayout;
|
export default RootLayout;
|
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
|
@ -17,27 +17,37 @@ export function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps)
|
||||||
|
|
||||||
const selectedAccountData = accounts.find((account) => account.email === selectedAccount);
|
const selectedAccountData = accounts.find((account) => account.email === selectedAccount);
|
||||||
|
|
||||||
|
// Triangle icon
|
||||||
|
const TriangleIcon = () => (
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M24 22.525H0l12-21.05 12 21.05z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-2'}`}
|
className={`flex items-center w-full justify-between rounded-md px-2 py-1.5 hover:bg-gray-100 ${isCollapsed ? 'justify-center' : ''}`}
|
||||||
>
|
>
|
||||||
{selectedAccountData && (
|
<div className="flex items-center gap-2">
|
||||||
<>
|
<div className="bg-black rounded-md text-white p-1">
|
||||||
<div className="w-6 h-6 flex items-center justify-center">
|
<TriangleIcon />
|
||||||
{selectedAccountData.icon}
|
</div>
|
||||||
</div>
|
{!isCollapsed && (
|
||||||
{!isCollapsed && (
|
<span className="text-sm font-medium">Alicia Koch</span>
|
||||||
<span>{selectedAccountData.label}</span>
|
)}
|
||||||
)}
|
</div>
|
||||||
</>
|
{!isCollapsed && (
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m6 9 6 6 6-6" />
|
||||||
|
</svg>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="absolute z-10 mt-2 w-56 bg-white rounded-md shadow-lg">
|
<div className="absolute z-10 top-full left-0 mt-1 w-64 rounded-md border shadow-md bg-white">
|
||||||
<div className="p-2">
|
<div className="p-1">
|
||||||
{accounts.map((account) => (
|
{accounts.map((account) => (
|
||||||
<button
|
<button
|
||||||
key={account.email}
|
key={account.email}
|
||||||
|
@ -45,12 +55,12 @@ export function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps)
|
||||||
setSelectedAccount(account.email);
|
setSelectedAccount(account.email);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}}
|
}}
|
||||||
className="w-full text-left px-3 py-2 hover:bg-gray-100 flex items-center space-x-2"
|
className="w-full text-left px-2 py-1.5 hover:bg-gray-100 rounded-md flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<div className="w-5 h-5 flex items-center justify-center">
|
<div className="w-6 h-6 flex items-center justify-center">
|
||||||
{account.icon}
|
{account.icon}
|
||||||
</div>
|
</div>
|
||||||
<span>{account.label}</span>
|
<span className="text-sm">{account.label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
interface Mail {
|
interface Mail {
|
||||||
|
@ -8,6 +8,8 @@ interface Mail {
|
||||||
email: string;
|
email: string;
|
||||||
date: string;
|
date: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
read?: boolean;
|
||||||
|
labels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MailDisplayProps {
|
interface MailDisplayProps {
|
||||||
|
@ -15,12 +17,10 @@ interface MailDisplayProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MailDisplay({ mail }: MailDisplayProps) {
|
export function MailDisplay({ mail }: MailDisplayProps) {
|
||||||
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
|
|
||||||
|
|
||||||
if (!mail) {
|
if (!mail) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<p>No message selected</p>
|
<p className="text-gray-500">No message selected</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export function MailDisplay({ mail }: MailDisplayProps) {
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex items-center justify-between p-4 border-b">
|
<div className="flex items-center justify-between p-4 border-b">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center text-gray-700">
|
||||||
{mail.name[0]}
|
{mail.name[0]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -39,7 +39,7 @@ export function MailDisplay({ mail }: MailDisplayProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm text-gray-500">
|
||||||
{format(new Date(mail.date), 'MMM d, yyyy h:mm a')}
|
{format(new Date(mail.date), 'MMM d, yyyy, h:mm a')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -49,16 +49,16 @@ export function MailDisplay({ mail }: MailDisplayProps) {
|
||||||
|
|
||||||
<div className="p-4 border-t">
|
<div className="p-4 border-t">
|
||||||
<textarea
|
<textarea
|
||||||
placeholder={`Reply ${mail.name}...`}
|
placeholder={`Reply to ${mail.name}...`}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between items-center mt-2">
|
<div className="flex justify-between items-center mt-4">
|
||||||
<label className="flex items-center space-x-2">
|
<label className="flex items-center space-x-2 text-sm text-gray-700">
|
||||||
<input type="checkbox" />
|
<input type="checkbox" className="rounded text-blue-500" />
|
||||||
<span className="text-sm">Mute this thread</span>
|
<span>Mute this thread</span>
|
||||||
</label>
|
</label>
|
||||||
<button className="px-4 py-2 bg-blue-500 text-white rounded">
|
<button className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,36 +14,38 @@ interface Mail {
|
||||||
|
|
||||||
interface MailListProps {
|
interface MailListProps {
|
||||||
items: Mail[];
|
items: Mail[];
|
||||||
selectedId?: string;
|
selectedId?: string | null;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MailList({ items, selectedId, onSelect }: MailListProps) {
|
export function MailList({ items, selectedId, onSelect }: MailListProps) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="divide-y">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => onSelect(item.id)}
|
onClick={() => onSelect(item.id)}
|
||||||
className={`p-4 border rounded cursor-pointer ${selectedId === item.id ? 'bg-gray-100' : ''}`}
|
className={`p-3 cursor-pointer ${
|
||||||
|
selectedId === item.id ? 'bg-gray-100' : ''
|
||||||
|
} hover:bg-gray-50`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-2">
|
||||||
<div className={`w-2 h-2 rounded-full ${item.read ? 'bg-transparent' : 'bg-blue-500'}`} />
|
<div className={`w-2 h-2 rounded-full ${item.read ? 'bg-transparent' : 'bg-blue-500'}`} />
|
||||||
<h3 className="font-medium">{item.name}</h3>
|
<h3 className="font-medium text-sm">{item.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
{formatDistanceToNow(new Date(item.date), { addSuffix: true })}
|
{formatDistanceToNow(new Date(item.date), { addSuffix: true })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-medium mt-1">{item.subject}</h4>
|
<h4 className="font-medium text-sm mt-1">{item.subject}</h4>
|
||||||
<p className="text-sm text-gray-500 mt-1 truncate">
|
<p className="text-xs text-gray-500 mt-1 truncate">
|
||||||
{item.text.substring(0, 100)}...
|
{item.text.substring(0, 100)}...
|
||||||
</p>
|
</p>
|
||||||
{item.labels.length > 0 && (
|
{item.labels.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-1 mt-2">
|
<div className="flex flex-wrap gap-1 mt-2">
|
||||||
{item.labels.map((label) => (
|
{item.labels.map((label) => (
|
||||||
<span key={label} className="px-2 py-1 text-xs bg-gray-200 rounded">
|
<span key={label} className="px-2 py-0.5 text-xs bg-gray-200 rounded-full">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -2,59 +2,230 @@ import React, { useState } from 'react';
|
||||||
import { AccountSwitcher } from './account-switcher';
|
import { AccountSwitcher } from './account-switcher';
|
||||||
import { MailList } from './mail-list';
|
import { MailList } from './mail-list';
|
||||||
import { MailDisplay } from './mail-display';
|
import { MailDisplay } from './mail-display';
|
||||||
|
import { Nav } from './nav';
|
||||||
import { mails, accounts } from '../data';
|
import { mails, accounts } from '../data';
|
||||||
|
|
||||||
export function Mail() {
|
export function Mail() {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
const [selectedMailId, setSelectedMailId] = useState<string | null>(mails[0].id);
|
||||||
const [activeTab, setActiveTab] = useState('all');
|
const [activeTab, setActiveTab] = useState('all');
|
||||||
const [selectedMailId, setSelectedMailId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const toggleCollapse = () => setIsCollapsed(!isCollapsed);
|
|
||||||
|
|
||||||
const filteredMails = activeTab === 'all'
|
const filteredMails = activeTab === 'all'
|
||||||
? mails
|
? mails
|
||||||
: mails.filter(mail => !mail.read);
|
: mails.filter(mail => !mail.read);
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
const ArchiveIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect width="20" height="5" x="2" y="3" rx="1" />
|
||||||
|
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
|
||||||
|
<path d="M10 12h4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const TrashIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M3 6h18" />
|
||||||
|
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||||
|
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const DeleteIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M20 6H4" />
|
||||||
|
<path d="M10 11v6" />
|
||||||
|
<path d="M14 11v6" />
|
||||||
|
<path d="M3 6h2l1.5 14a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2L21 6h2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ClockIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ArrowLeftIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m12 19-7-7 7-7" />
|
||||||
|
<path d="M19 12H5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ArrowRightIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M5 12h14" />
|
||||||
|
<path d="m12 5 7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MoreIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="1" />
|
||||||
|
<circle cx="19" cy="12" r="1" />
|
||||||
|
<circle cx="5" cy="12" r="1" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SearchIcon = () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="11" cy="11" r="8" />
|
||||||
|
<path d="m21 21-4.3-4.3" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{
|
||||||
|
title: "Inbox",
|
||||||
|
label: "128",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
|
||||||
|
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "default" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Drafts",
|
||||||
|
label: "9",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||||
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "ghost" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sent",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m22 2-7 20-4-9-9-4Z" />
|
||||||
|
<path d="M22 2 11 13" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "ghost" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Junk",
|
||||||
|
label: "23",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect width="20" height="5" x="2" y="3" rx="1" />
|
||||||
|
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
|
||||||
|
<path d="M10 12h4" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "ghost" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Trash",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M3 6h18" />
|
||||||
|
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
||||||
|
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "ghost" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archive",
|
||||||
|
icon: (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect width="20" height="5" x="2" y="3" rx="1" />
|
||||||
|
<path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" />
|
||||||
|
<path d="M10 12h4" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
variant: "ghost" as const,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen bg-white">
|
||||||
<div className={`${isCollapsed ? 'w-16' : 'w-64'} border-r flex flex-col`}>
|
{/* Sidebar */}
|
||||||
|
<div className={`${isCollapsed ? 'w-16' : 'w-64'} border-r flex flex-col transition-all duration-200 bg-white`}>
|
||||||
<div className="p-4 border-b">
|
<div className="p-4 border-b">
|
||||||
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
|
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="flex-1 overflow-auto py-2">
|
||||||
<button onClick={toggleCollapse} className="w-full text-left">
|
<Nav links={navItems} isCollapsed={isCollapsed} />
|
||||||
{isCollapsed ? '»' : '« Collapse'}
|
</div>
|
||||||
|
<div className="p-4 border-t">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||||
|
className="w-full flex items-center justify-center text-sm text-gray-500 hover:text-gray-900"
|
||||||
|
>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m9 18 6-6-6-6" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="m15 18-6-6 6-6" />
|
||||||
|
</svg>
|
||||||
|
<span className="ml-2">Collapse</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col border-r">
|
{/* Mail list */}
|
||||||
<div className="p-4 border-b">
|
<div className="w-80 border-r flex flex-col">
|
||||||
|
<div className="p-4 border-b flex flex-col gap-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-xl font-bold">Inbox</h2>
|
<h2 className="text-xl font-semibold">Inbox</h2>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-1">
|
||||||
<button
|
<button className={`px-3 py-1.5 text-sm rounded-md ${activeTab === 'all' ? 'bg-gray-100 text-gray-900' : 'text-gray-500'}`} onClick={() => setActiveTab('all')}>
|
||||||
onClick={() => setActiveTab('all')}
|
|
||||||
className={`px-3 py-1 rounded ${activeTab === 'all' ? 'bg-blue-500 text-white' : ''}`}
|
|
||||||
>
|
|
||||||
All mail
|
All mail
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className={`px-3 py-1.5 text-sm rounded-md ${activeTab === 'unread' ? 'bg-gray-100 text-gray-900' : 'text-gray-500'}`} onClick={() => setActiveTab('unread')}>
|
||||||
onClick={() => setActiveTab('unread')}
|
|
||||||
className={`px-3 py-1 rounded ${activeTab === 'unread' ? 'bg-blue-500 text-white' : ''}`}
|
|
||||||
>
|
|
||||||
Unread
|
Unread
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
|
||||||
|
<div className="flex relative">
|
||||||
|
<SearchIcon className="absolute left-3 top-2.5 h-4 w-4 text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="w-full p-2 border rounded"
|
className="w-full py-2 pl-9 pr-4 border rounded-md text-sm bg-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-1 border-b p-2">
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<ArchiveIcon />
|
||||||
|
</button>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<TrashIcon />
|
||||||
|
</button>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<DeleteIcon />
|
||||||
|
</button>
|
||||||
|
<span className="flex-1"></span>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<ClockIcon />
|
||||||
|
</button>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<ArrowLeftIcon />
|
||||||
|
</button>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<ArrowRightIcon />
|
||||||
|
</button>
|
||||||
|
<button className="p-1 rounded-md hover:bg-gray-100">
|
||||||
|
<MoreIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<MailList
|
<MailList
|
||||||
items={filteredMails}
|
items={filteredMails}
|
||||||
|
@ -64,6 +235,7 @@ export function Mail() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mail content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<MailDisplay mail={mails.find(mail => mail.id === selectedMailId) || null} />
|
<MailDisplay mail={mails.find(mail => mail.id === selectedMailId) || null} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,18 +12,20 @@ interface NavProps {
|
||||||
|
|
||||||
export function Nav({ links, isCollapsed }: NavProps) {
|
export function Nav({ links, isCollapsed }: NavProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex ${isCollapsed ? 'flex-col items-center' : 'flex-col'}`}>
|
<div className="space-y-1 px-2">
|
||||||
{links.map((link, index) => (
|
{links.map((link, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex items-center p-2 rounded ${link.variant === 'default' ? 'bg-gray-100' : ''} ${isCollapsed ? 'justify-center' : 'justify-between'}`}
|
className={`w-full flex items-center py-2 px-2 rounded-md text-sm ${
|
||||||
|
link.variant === 'default' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:bg-gray-100'
|
||||||
|
} ${isCollapsed ? 'justify-center' : 'justify-between'}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-3">
|
||||||
<span className="w-5 h-5 flex items-center justify-center">
|
<span className="w-5 h-5 flex items-center justify-center">
|
||||||
{link.icon}
|
{link.icon}
|
||||||
</span>
|
</span>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<span className="ml-2">{link.title}</span>
|
<span>{link.title}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isCollapsed && link.label && (
|
{!isCollapsed && link.label && (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export const mails = [
|
export const mails = [
|
||||||
{
|
{
|
||||||
id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
|
id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
|
||||||
|
@ -19,7 +21,16 @@ export const mails = [
|
||||||
read: true,
|
read: true,
|
||||||
labels: ["work", "important"],
|
labels: ["work", "important"],
|
||||||
},
|
},
|
||||||
// More mails...
|
{
|
||||||
|
id: "3e7c3f6d-bdf5-46ae-8d90-171300f27ae2",
|
||||||
|
name: "Bob Johnson",
|
||||||
|
email: "bob@example.com",
|
||||||
|
subject: "Weekend Plans",
|
||||||
|
text: "Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's supposed to be great weather and the fall colors should be at their peak.\n\nLet me know if you'd like to join. I'm planning to start early Saturday morning.\n\nCheers, Bob",
|
||||||
|
date: "2023-10-21T18:45:00",
|
||||||
|
read: false,
|
||||||
|
labels: ["personal"],
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const accounts = [
|
export const accounts = [
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { mails } from './data';
|
||||||
|
|
||||||
export function useMail() {
|
export function useMail() {
|
||||||
const [mail, setMail] = useState({
|
const [mail, setMail] = useState({
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import RootLayout from ".";
|
import RootLayout from ".";
|
||||||
|
import './styles/globals.css' // or your CSS file path
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
3
src/styles/globals.css
Normal file
3
src/styles/globals.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
201
styles.js
201
styles.js
|
@ -1,201 +0,0 @@
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
export const colors = {
|
|
||||||
|
|
||||||
primary: '#8A4FFF',
|
|
||||||
secondary: '#FFD700',
|
|
||||||
background: '#2A1B3D',
|
|
||||||
white: '#FFFFFF',
|
|
||||||
purple: {
|
|
||||||
dark: '#1A0B2E',
|
|
||||||
medium: '#431E6E',
|
|
||||||
light: '#8A4FFF'
|
|
||||||
},
|
|
||||||
gold: {
|
|
||||||
light: '#FFD700',
|
|
||||||
medium: '#DAA520',
|
|
||||||
dark: '#B8860B'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const glassEffect = {
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
backdropFilter: 'blur(10px)',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const globalStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
// Other global container styles
|
|
||||||
},
|
|
||||||
backgroundArcImage: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '355px',
|
|
||||||
height: '380px',
|
|
||||||
|
|
||||||
// Other global background image styles
|
|
||||||
},
|
|
||||||
backgroundImage: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
// Other global background image styles
|
|
||||||
},
|
|
||||||
safeArea: {
|
|
||||||
flex: 1,
|
|
||||||
// Other global safe area styles
|
|
||||||
},
|
|
||||||
topNavContainer: {
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
innerNavContainer: {
|
|
||||||
width: '90%',
|
|
||||||
paddingTop: 20,
|
|
||||||
marginTop: -30,
|
|
||||||
borderRadius: 20,
|
|
||||||
height: 85,
|
|
||||||
marginLeft: '5%',
|
|
||||||
marginRight: '5%',
|
|
||||||
paddingRight: '20px',
|
|
||||||
|
|
||||||
paddingLeft: '20px',
|
|
||||||
backgroundColor: 'white',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
logoImage: {
|
|
||||||
left: 8,
|
|
||||||
top:4,
|
|
||||||
width: 80,
|
|
||||||
height: 20,
|
|
||||||
},
|
|
||||||
instagramIcon: {
|
|
||||||
right: 10,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
link:{
|
|
||||||
color:'white'
|
|
||||||
},
|
|
||||||
|
|
||||||
headerView:{
|
|
||||||
display: 'inline'
|
|
||||||
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
marginLeft: 8,
|
|
||||||
marginRight: 12,
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
},
|
|
||||||
// Layout styles
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'white'
|
|
||||||
},
|
|
||||||
|
|
||||||
safeArea: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
backgroundImage: {
|
|
||||||
top:0,
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
resizeMode: 'cover',
|
|
||||||
},
|
|
||||||
backgroundImageForm: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
contentContainer: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
card: {
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: 400,
|
|
||||||
padding: 15,
|
|
||||||
borderRadius: 15,
|
|
||||||
...glassEffect,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(255, 255, 255, 0.1)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Text styles
|
|
||||||
title: {
|
|
||||||
fontSize: 18,
|
|
||||||
flexWrap: 'nowrap',
|
|
||||||
fontWeight: '600',
|
|
||||||
marginBottom: 8,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: colors.white,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
},
|
|
||||||
|
|
||||||
subtitle: {
|
|
||||||
fontSize: 14,
|
|
||||||
marginBottom: 25,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: 'rgba(255, 255, 255, 0.8)',
|
|
||||||
letterSpacing: 0.3,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Input styles
|
|
||||||
input: {
|
|
||||||
width: '90%',
|
|
||||||
height: 38,
|
|
||||||
textAlign: 'center',
|
|
||||||
borderRadius: 8,
|
|
||||||
paddingHorizontal: 15,
|
|
||||||
marginBottom: 5,
|
|
||||||
marginRight: '5%',
|
|
||||||
marginLeft: '5%',
|
|
||||||
fontSize: 16,
|
|
||||||
backgroundColor: 'white',
|
|
||||||
color: colors.gray,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(255, 255, 255, 0.15)',
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
marginTop: 20,
|
|
||||||
paddingVertical: 12,
|
|
||||||
marginLeft:16,
|
|
||||||
marginRight: 16,
|
|
||||||
borderRadius: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: '#A855F7', // fallback color
|
|
||||||
// Add gradient-like effect using background image
|
|
||||||
backgroundImage: 'linear-gradient(to right, #7859a9, #deb99b)',
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
disabledButton: {
|
|
||||||
opacity: 0.7,
|
|
||||||
},
|
|
||||||
// Particle effects container
|
|
||||||
particleContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
59
tailwind.config.cjs
Normal file
59
tailwind.config.cjs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
module.exports = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
|
"./components/**/*.{js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
// Add all files that use Tailwind classes
|
|
||||||
"./src/**/*.css"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2020",
|
||||||
|
@ -9,9 +13,6 @@
|
||||||
],
|
],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": [
|
|
||||||
"@tauri-apps/api/tauri"
|
|
||||||
],
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
|
import path from "path";
|
||||||
|
import tailwindcss from "@tailwindcss/vite"
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
// @ts-expect-error process is a nodejs global
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [react()],
|
|
||||||
|
plugins: [react(),tailwindcss()],
|
||||||
css: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
config: false // Disable auto-loading of postcss.config.js
|
config: false // Disable auto-loading of postcss.config.js
|
||||||
} as any
|
} as any
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
//
|
//
|
||||||
// 1. prevent vite from obscuring rust errors
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
|
Loading…
Add table
Reference in a new issue