feat: migrate styles to Tailwind CSS, remove unused files, and add new layout components

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-04-02 20:42:47 -03:00
parent 5b92932d82
commit 63941e41c3
94 changed files with 1051 additions and 2851 deletions

2
.gitignore vendored
View file

@ -23,3 +23,5 @@ dist-ssr
*.sln
*.sw?
output.sh
.next
ui

View file

@ -1,3 +1,4 @@
"use client";
import React, { useState } from 'react';
import { UserAuthForm } from './components/user-auth-form';

View file

@ -1,3 +1,4 @@
"use client";
import React from 'react';
import { MessageList } from './message-list';
import { ChatInput } from './chat-input';

View file

@ -1,3 +1,5 @@
"use client";
import React from 'react';
import { VideoPlayer } from './video-player';
import { ImageViewer } from './image-viewer';

View file

@ -1,3 +1,4 @@
"use client";
import React from 'react';
import { useChat } from '../../providers/chat-provider';
import '../../styles/selector.css';

View file

@ -1,5 +1,6 @@
"use client";
import React, { useEffect, useState } from 'react';
import { soundAssets } from '../../../public/sounds/manifest';
import { soundAssets } from '../../../../public/sounds/manifest';
//import { cacheAssets } from '../lib/asset-loader';
export function SoundInitializer({ children }: { children: React.ReactNode }) {

View file

@ -4,7 +4,7 @@ import { ChatLayout } from './components/chat-layout';
import { SoundInitializer } from './components/sound-initializer';
import { SoundProvider } from './providers/sound-provider';
export function Chat() {
export default function Chat() {
return (
<SoundInitializer>
<SoundProvider>

View file

@ -1,3 +1,4 @@
"use client";
import React, { createContext, useContext, useState, useEffect } from 'react';
import { core } from '@tauri-apps/api';
import { User, ChatInstance } from '../types';

View file

@ -1,3 +1,4 @@
"use client";
import React, { createContext, useContext, useCallback } from 'react';
import { core } from '@tauri-apps/api';

36
app/client-nav.tsx Normal file
View file

@ -0,0 +1,36 @@
"use client";
import { usePathname, useRouter } from 'next/navigation';
import { Button } from '../src/components/ui/button';
const examples = [
{ name: "Home", href: "/authentication" },
{ name: "Dashboard", href: "/dashboard" },
{ name: "Chat", href: "/chat" },
{ name: "Mail", href: "/mail" },
{ name: "Drive", href: "/drive" },
{ name: "Tasks", href: "/tasks" },
{ name: "Templates", href: "/templates" },
{ name: "Settings", href: "/sync" },
];
export function Nav() {
const pathname = usePathname();
const router = useRouter();
return (
<div className="examples-nav-container">
<div className="examples-nav-inner">
{examples.map((example) => (
<Button
key={example.href}
onClick={() => router.push(example.href)}
className={`example-button ${pathname === example.href ? 'active' : ''}`}
>
{example.name}
</Button>
))}
</div>
</div>
);
}

View file

@ -1,3 +1,4 @@
"use client";
import React, { useState } from 'react';
import { format } from 'date-fns';

View file

@ -1,3 +1,5 @@
"use client";
import React, { useState } from 'react';
const groups = [

View file

@ -1,3 +1,5 @@
"use client";
import React, { useState } from 'react';
export function UserNav() {

View file

@ -1,9 +1,10 @@
"use client";
import { useState } from 'react';
import { FileTree } from './components/FileTree';
import { FileBrowser } from './components/FileBrowser';
import { FileOperations } from './components/FileOperations';
export function DriveScreen() {
export default function DriveScreen() {
const [currentPath, setCurrentPath] = useState('');
const [refreshKey, setRefreshKey] = useState(0);

17
app/layout.tsx Normal file
View file

@ -0,0 +1,17 @@
import { Nav } from './client-nav';
import './globals.css';
import './globals.css' // This path is correct if the file is in your src/app directory
import { ReactNode } from 'react'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Nav />
{children}
</body>
</html>
)
}

View file

@ -42,7 +42,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Mail } from "@/mail/data"
import { Mail } from "./data"
interface MailDisplayProps {
mail: Mail | null

View file

@ -4,8 +4,8 @@ import formatDistanceToNow from "date-fns/formatDistanceToNow"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Mail } from "@/mail/data"
import { useMail } from "@/mail/use-mail"
import { Mail } from "../data"
import { useMail } from "../use-mail"
interface MailListProps {
items: Mail[]

View file

@ -30,12 +30,12 @@ import {
TabsTrigger,
} from "@/components/ui/tabs"
import { TooltipProvider } from "@/components/ui/tooltip"
import { AccountSwitcher } from "@/mail/components/account-switcher"
import { MailDisplay } from "@/mail/components/mail-display"
import { MailList } from "@/mail/components/mail-list"
import { Nav } from "@/mail/components/nav"
import { type Mail } from "@/mail/data"
import { useMail } from "@/mail/use-mail"
import { AccountSwitcher } from "./account-switcher"
import { MailDisplay } from "./mail-display"
import { MailList } from "./mail-list"
import { Nav } from "./nav"
import { type Mail } from "../data"
import { useMail } from "../use-mail"
interface MailProps {
accounts: {

View file

@ -1,6 +1,7 @@
"use client"
import { Link } from 'lucide-react';
import NextLink from 'next/link'; // Changed from lucide-react
import { Link as LinkIcon } from 'lucide-react'; // Renamed to avoid conflict
import { LucideIcon } from "lucide-react"
import { cn } from "@/lib/utils"
@ -32,7 +33,7 @@ export function Nav({ links, isCollapsed }: NavProps) {
isCollapsed ? (
<Tooltip key={index} delayDuration={0}>
<TooltipTrigger asChild>
<Link
<NextLink
href="#"
className={cn(
buttonVariants({ variant: link.variant, size: "icon" }),
@ -43,7 +44,7 @@ export function Nav({ links, isCollapsed }: NavProps) {
>
<link.icon className="h-4 w-4" />
<span className="sr-only">{link.title}</span>
</Link>
</NextLink>
</TooltipTrigger>
<TooltipContent side="right" className="flex items-center gap-4">
{link.title}
@ -55,7 +56,7 @@ export function Nav({ links, isCollapsed }: NavProps) {
</TooltipContent>
</Tooltip>
) : (
<Link
<NextLink
key={index}
href="#"
className={cn(
@ -78,7 +79,7 @@ export function Nav({ links, isCollapsed }: NavProps) {
{link.label}
</span>
)}
</Link>
</NextLink>
)
)}
</nav>

27
app/mail/page.tsx Normal file
View file

@ -0,0 +1,27 @@
import Image from "next/image"
import { Mail } from "./components/mail"
import { accounts, mails } from "./data"
export default function MailPage() {
const layout = [20, 32, 48]; //cookies().get("react-resizable-panels:layout:mail")
const collapsed = false; //cookies().get("react-resizable-panels:collapsed")
const defaultLayout = layout// ? JSON.parse(layout.value) : undefined
const defaultCollapsed = collapsed //? JSON.parse(collapsed.value) : undefined
return (
<>
<div className="flex-col md:flex">
<Mail
accounts={accounts}
mails={mails}
defaultLayout={defaultLayout}
defaultCollapsed={defaultCollapsed}
navCollapsedSize={4}
/>
</div>
</>
)
}

View file

@ -1,6 +1,6 @@
import { atom, useAtom } from "jotai"
import { Mail, mails } from "@/mail/data"
import { Mail, mails } from "./data"
type Config = {
selected: Mail["id"] | null

9
app/page.tsx Normal file
View file

@ -0,0 +1,9 @@
// app/page.tsx (your home page)
export default function Home() {
return (
<main className="min-h-screen p-8">
<h1 className="text-3xl font-bold">Welcome to My Tauri App</h1>
<p className="mt-4">This is your home page</p>
</main>
)
}

View file

@ -1,3 +1,4 @@
"use client";
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";

View file

@ -1,3 +1,5 @@
"use client";
import React, { useState } from 'react';
import { Task } from '../data/schema';
import { labels, priorities, statuses } from '../data/data';

View file

@ -1,7 +0,0 @@
module.exports = function (api) {
api.cache(true);
return {
plugins: ['nativewind/babel']
};
};

12
gbclient.code-workspace Normal file
View file

@ -0,0 +1,12 @@
{
"folders": [
{
"path": "."
},
{
"name": "ui",
"path": "../../ui"
}
],
"settings": {}
}

5
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

7
next.config.js Normal file
View file

@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: { unoptimized: true }
}
module.exports = nextConfig

1023
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,16 +2,15 @@
"name": "gbclient",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "npx tailwindcss -i ./src/styles/globals.css -o ./public/output.css;ESBUILD_RUNNER=true vite",
"build": "tsc && vite build",
"preview": "vite preview",
"dev": "npx tailwindcss -i ./src/styles/globals.css -o ./public/output.css;ESBUILD_RUNNER=true next dev",
"build": "tsc && next build",
"start": "next start",
"tauri": "tauri"
},
"dependencies": {
"@babel/runtime": "7.26.0",
"@hookform/resolvers": "^3.9.1",
"@next/font": "^14.2.15",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-aspect-ratio": "^1.1.2",
@ -52,6 +51,7 @@
"jotai": "^2.12.2",
"lucide-react": "0.454.0",
"nativewind": "2.0.10",
"next": "^15.2.4",
"postcss": "8.4.35",
"react": "18.3.1",
"react-day-picker": "^8.10.1",
@ -59,21 +59,18 @@
"react-hook-form": "^7.53.2",
"react-markdown": "10.1.0",
"react-resizable-panels": "^2.1.7",
"react-router-dom": "7.4.1",
"tailwind-merge": "3.0.2",
"tailwindcss-animate": "1.0.7",
"uuid": "11.0.3",
"zod": "^3.21.4"
},
"devDependencies": {
"@babel/core": "7.18.6",
"@tauri-apps/cli": "2",
"@types/jest": "29.5.12",
"@types/node": "22.13.14",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@types/react-test-renderer": "18.0.7",
"@vitejs/plugin-react": "4.3.4",
"copy-webpack-plugin": "12.0.2",
"esbuild-runner": "2.2.2",
"jest": "29.2.1",
@ -81,7 +78,6 @@
"postcss-load-config": "6.0.1",
"react-test-renderer": "18.2.0",
"tailwindcss": "3.1.8",
"typescript": "5.6.2",
"vite": "6.0.3"
"typescript": "5.6.2"
}
}

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,10 @@
"version": "6.0.0",
"identifier": "online.generalbots",
"build": {
"beforeDevCommand": "pnpm dev",
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "pnpm build",
"frontendDist": "../dist"
"beforeBuildCommand": "npm run build",
"frontendDist": "../out"
},
"app": {
"withGlobalTauri": true,

View file

@ -1,51 +0,0 @@
import React from 'react';
import '../../styles/ui.css';
const EMOJI_CATEGORIES = {
"😀 🎮": ["😀", "😎", "🤖", "👾", "🎮", "✨", "🚀", "💫"],
"🌟 💫": ["⭐", "🌟", "💫", "✨", "⚡", "💥", "🔥", "🌈"],
"🤖 🎯": ["🤖", "🎯", "🎲", "🎮", "🕹️", "👾", "💻", "⌨️"]
};
export function EmojiPicker({ visible, onClose, onEmojiSelect }: {
visible: boolean;
onClose: () => void;
onEmojiSelect: (emoji: string) => void;
}) {
if (!visible) return null;
return (
<div className="emoji-picker-modal">
<div className="emoji-picker-header">
<h3>Select Emoji</h3>
<button onClick={onClose} className="close-button">
<svg viewBox="0 0 24 24">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<div className="emoji-picker-content">
{Object.entries(EMOJI_CATEGORIES).map(([category, emojis]) => (
<div key={category} className="emoji-category">
<h4 className="category-title">{category}</h4>
<div className="emoji-grid">
{emojis.map(emoji => (
<button
key={emoji}
className="emoji-button"
onClick={() => {
onEmojiSelect(emoji);
onClose();
}}
>
{emoji}
</button>
))}
</div>
</div>
))}
</div>
</div>
);
}

View file

@ -1,53 +0,0 @@
import React from 'react';
import { useSound } from '../../providers/sound-provider';
import '../../styles/ui.css';
export function SettingsPanel() {
const [theme, setTheme] = React.useState('dark');
const [sound, setSound] = React.useState(true);
const [powerMode, setPowerMode] = React.useState(false);
const { setEnabled, playSound } = useSound();
const handleSoundToggle = (value: boolean) => {
setSound(value);
setEnabled(value);
if (value) {
playSound('success');
}
};
const handleThemeChange = () => {
playSound('click');
setTheme(theme === 'dark' ? 'light' : 'dark');
};
const handlePowerMode = (value: boolean) => {
playSound(value ? 'success' : 'click');
setPowerMode(value);
};
return (
<div className="settings-panel">
<div className="settings-option">
{sound ? (
<svg className="icon" viewBox="0 0 24 24">
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3"/>
</svg>
) : (
<svg className="icon" viewBox="0 0 24 24">
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3M3 3l18 18"/>
</svg>
)}
<span className="option-text">Sound Effects</span>
<label className="switch">
<input
type="checkbox"
checked={sound}
onChange={(e) => handleSoundToggle(e.target.checked)}
/>
<span className="slider"></span>
</label>
</div>
</div>
);
}

View file

@ -1,99 +0,0 @@
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import AuthenticationScreen from './authentication';
import { Chat } from './chat';
import DashboardPage from './dashboard';
import TaskPage from './tasks';
import TemplatesPage from './templates';
import { DriveScreen } from './drive';
import SyncPage from './sync/page';
import { Button } from './components/ui/button';
import MailPage from './mail';
const examples = [
{ name: "Home", href: "authentication" },
{ name: "Dashboard", href: "dashboard" },
{ name: "Chat", href: "chat" },
{ name: "Mail", href: "mail" },
{ name: "Drive", href: "drive" },
{ name: "Tasks", href: "tasks" },
{ name: "Meet", href: "meet" },
{ name: "Templates", href: "templates" },
{ name: "Settings", href: "sync" },
{ 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() {
return (
<div className="app-container">
<ExamplesNav />
<main className="app-main">
<Routes>
<Route path="authentication" element={
<>
<AuthenticationScreen />
</>
} />
<Route path="chat" element={
<>
<Chat />
</>
} />
<Route path="dashboard" element={
<>
<DashboardPage />
</>
} />
<Route path="mail" element={
<>
<MailPage />
</>
} />
<Route path="drive" element={<DriveScreen />} />
<Route path="tasks" element={
<>
<TaskPage />
</>
} />
<Route path="meet" element={<div>Meet Screen (Placeholder)</div>} />
<Route path="templates" element={
<>
<TemplatesPage />
</>
} />
<Route path="sync" element={<SyncPage />} />
</Routes>
</main>
</div>
);
}
export default RootLayout;

25
src/lib/client-cookies.ts Normal file
View file

@ -0,0 +1,25 @@
'use client';
export function getClientCookie(key: string): string | null {
if (typeof document === 'undefined') return null;
const value = document.cookie
.split('; ')
.find(row => row.startsWith(`${key}=`))
?.split('=')[1];
return value ? decodeURIComponent(value) : null;
}
export function setClientCookie(
key: string,
value: string,
options: { expires?: Date; path?: string } = {}
): void {
if (typeof document === 'undefined') return;
let cookie = `${key}=${encodeURIComponent(value)}`;
if (options.expires) cookie += `; expires=${options.expires.toUTCString()}`;
cookie += `; path=${options.path || '/'}`;
document.cookie = cookie;
}

View file

@ -0,0 +1,11 @@
'use server';
import { cookies } from 'next/headers';
export async function getServerCookie(key: string): Promise<string | undefined> {
// This will only run during SSR, not during static export
if (process.env.NEXT_PHASE === 'phase-production-build') {
return undefined;
}
return cookies().get(key)?.value;
}

View file

@ -1,48 +0,0 @@
import { useState, useEffect } from "react";
import { Mail } from "@/mail/components/mail";
import { accounts, mails } from "@/mail/data";
export default function MailPage() {
const [defaultLayout, setDefaultLayout] = useState(undefined);
const [defaultCollapsed, setDefaultCollapsed] = useState(undefined);
useEffect(() => {
// In Vite/React, we'll use localStorage instead of cookies
const layout = localStorage.getItem("react-resizable-panels:layout:mail");
const collapsed = localStorage.getItem("react-resizable-panels:collapsed:mail");
if (layout) setDefaultLayout(JSON.parse(layout));
if (collapsed) setDefaultCollapsed(JSON.parse(collapsed));
}, []);
return (
<>
<div className="md:hidden">
<img
src="/examples/mail-dark.png"
width={1280}
height={727}
alt="Mail"
className="dark:block"
/>
<img
src="/examples/mail-light.png"
width={1280}
height={727}
alt="Mail"
className="block dark:hidden"
/>
</div>
<div className="flex-col md:flex">
<Mail
accounts={accounts}
mails={mails}
defaultLayout={defaultLayout}
defaultCollapsed={defaultCollapsed}
navCollapsedSize={4}
/>
</div>
</>
);
}

View file

@ -1,13 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import RootLayout from ".";
import './styles/globals.css' // or your CSS file path
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<RootLayout />
</BrowserRouter>
</React.StrictMode>
)

1
src/vite-env.d.ts vendored
View file

@ -1 +0,0 @@
/// <reference types="vite/client" />

View file

@ -2,7 +2,9 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
},
"target": "ES2020",
"useDefineForClassFields": true,
@ -19,19 +21,33 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsx": "preserve",
/* Linting */
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"incremental": true,
"esModuleInterop": true,
"plugins": [
{
"name": "next"
}
]
},
"include": [
"src"
],
"src",
".next/types/**/*.ts"
, "app" ],
"references": [
{
"path": "./tsconfig.node.json"
}
],
"exclude": [
"node_modules",
"src-rust"
]
}

View file

@ -1,45 +0,0 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import tailwindcss from "@tailwindcss/vite"
// @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [react(),tailwindcss()],
css: {
postcss: {
config: false // Disable auto-loading of postcss.config.js
} as any
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 1421,
}
: undefined,
watch: {
// 3. tell vite to ignore watching `src-rust`
ignored: ["**/src-rust/**"],
},
},
}));