From 7b7b180f967d51e7777d630c191fd1cd16daddd1 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Wed, 15 Apr 2026 09:25:58 +0200 Subject: [PATCH] feat: scaffold apps/clients Cloudflare Worker app --- apps/clients/index.html | 12 + apps/clients/package.json | 50 + apps/clients/src/App.tsx | 9 + apps/clients/src/i18n.ts | 31 + apps/clients/src/locales/en/booking.json | 3 + apps/clients/src/locales/fr/booking.json | 3 + apps/clients/src/main.css | 1266 ++++++++++++++++++++++ apps/clients/src/main.tsx | 28 + apps/clients/src/routes.tsx | 11 + apps/clients/tsconfig.json | 30 + apps/clients/vite.config.ts | 22 + apps/clients/worker/index.ts | 9 + apps/clients/wrangler.toml | 16 + package.json | 1 + 14 files changed, 1491 insertions(+) create mode 100644 apps/clients/index.html create mode 100644 apps/clients/package.json create mode 100644 apps/clients/src/App.tsx create mode 100644 apps/clients/src/i18n.ts create mode 100644 apps/clients/src/locales/en/booking.json create mode 100644 apps/clients/src/locales/fr/booking.json create mode 100644 apps/clients/src/main.css create mode 100644 apps/clients/src/main.tsx create mode 100644 apps/clients/src/routes.tsx create mode 100644 apps/clients/tsconfig.json create mode 100644 apps/clients/vite.config.ts create mode 100644 apps/clients/worker/index.ts create mode 100644 apps/clients/wrangler.toml diff --git a/apps/clients/index.html b/apps/clients/index.html new file mode 100644 index 0000000..9fb3e81 --- /dev/null +++ b/apps/clients/index.html @@ -0,0 +1,12 @@ + + + + + + Xtablo — Client Portal + + +
+ + + diff --git a/apps/clients/package.json b/apps/clients/package.json new file mode 100644 index 0000000..088606a --- /dev/null +++ b/apps/clients/package.json @@ -0,0 +1,50 @@ +{ + "name": "@xtablo/clients", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite dev --port 5175", + "build": "tsc -b && vite build --mode production", + "build:staging": "tsc -b && vite build --mode staging", + "build:prod": "tsc -b && vite build --mode production", + "deploy": "wrangler deploy", + "typecheck": "tsc -b", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "preview": "vite preview", + "clean": "rm -rf dist .vite tsconfig.tsbuildinfo node_modules/.vite" + }, + "devDependencies": { + "@biomejs/biome": "2.2.5", + "@cloudflare/vite-plugin": "^1.9.4", + "@tailwindcss/vite": "^4.0.14", + "@types/react": "19.0.10", + "@types/react-dom": "19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "tailwindcss": "^4.0.14", + "tw-animate-css": "^1.4.0", + "typescript": "^5.7.0", + "vite": "^6.2.2", + "vite-tsconfig-paths": "^5.1.4", + "wrangler": "^4.24.3" + }, + "dependencies": { + "@tanstack/react-query": "^5.69.0", + "@xtablo/shared": "workspace:*", + "@xtablo/shared-types": "workspace:*", + "@xtablo/tablo-views": "workspace:*", + "@xtablo/ui": "workspace:*", + "@xtablo/chat-ui": "workspace:*", + "i18next": "^25.6.0", + "i18next-browser-languagedetector": "^8.2.0", + "lucide-react": "^0.460.0", + "react": "19.0.0", + "react-dom": "19.0.0", + "react-i18next": "^16.2.0", + "react-router-dom": "^7.9.4", + "tailwind-merge": "^3.0.2", + "zustand": "^5.0.5" + } +} diff --git a/apps/clients/src/App.tsx b/apps/clients/src/App.tsx new file mode 100644 index 0000000..3683784 --- /dev/null +++ b/apps/clients/src/App.tsx @@ -0,0 +1,9 @@ +import AppRoutes from "./routes"; + +export default function App() { + return ( +
+ +
+ ); +} diff --git a/apps/clients/src/i18n.ts b/apps/clients/src/i18n.ts new file mode 100644 index 0000000..334b18e --- /dev/null +++ b/apps/clients/src/i18n.ts @@ -0,0 +1,31 @@ +import i18n from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import { initReactI18next } from "react-i18next"; +import bookingEn from "./locales/en/booking.json"; +// Import translation files +import bookingFr from "./locales/fr/booking.json"; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources: { + fr: { + booking: bookingFr, + }, + en: { + booking: bookingEn, + }, + }, + fallbackLng: "fr", + defaultNS: "booking", + interpolation: { + escapeValue: false, + }, + detection: { + order: ["localStorage", "navigator"], + caches: ["localStorage"], + }, + }); + +export default i18n; diff --git a/apps/clients/src/locales/en/booking.json b/apps/clients/src/locales/en/booking.json new file mode 100644 index 0000000..5c560df --- /dev/null +++ b/apps/clients/src/locales/en/booking.json @@ -0,0 +1,3 @@ +{ + "welcome": "Welcome" +} diff --git a/apps/clients/src/locales/fr/booking.json b/apps/clients/src/locales/fr/booking.json new file mode 100644 index 0000000..ead2829 --- /dev/null +++ b/apps/clients/src/locales/fr/booking.json @@ -0,0 +1,3 @@ +{ + "welcome": "Bienvenue" +} diff --git a/apps/clients/src/main.css b/apps/clients/src/main.css new file mode 100644 index 0000000..a896ff7 --- /dev/null +++ b/apps/clients/src/main.css @@ -0,0 +1,1266 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --color-navbar-background: #292e39; + --color-navbar-darker: #171920; +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +.str-chat { + --str-chat__primary-color: #8b7396; + --str-chat__active-primary-color: #6e5c7d; + --str-chat__surface-color: #f5f3f7; + --str-chat__secondary-surface-color: #e8e4ec; + --str-chat__primary-surface-color: #ebe7f0; + --str-chat__primary-surface-color-low-emphasis: #f2f0f5; + --str-chat__border-radius-circle: 6px; +} + +.dark .str-chat { + --str-chat__primary-color: #a68bb5; + --str-chat__active-primary-color: #8b7396; + --str-chat__surface-color: rgba(120, 107, 130, 0.25); + --str-chat__secondary-surface-color: rgba(140, 130, 150, 0.18); + --str-chat__primary-surface-color: rgba(166, 139, 181, 0.12); + --str-chat__primary-surface-color-low-emphasis: rgba(166, 139, 181, 0.06); + --str-chat__background-color: rgba(110, 100, 120, 0.2); + --str-chat__secondary-background-color: rgba(80, 72, 88, 0.35); + --str-chat__border-color: rgba(120, 107, 130, 0.28); + --str-chat__text-color: #f5f3f7; + --str-chat__text-low-emphasis-color: #b8b0c0; + --str-chat__disabled-color: rgba(155, 143, 165, 0.35); +} + +@keyframes gradient-x { + 0%, + 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} + +.animate-gradient-x { + animation: gradient-x 15s ease infinite; +} + +@keyframes wave-float { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 25% { + transform: translateY(-20px) rotate(1deg); + } + 50% { + transform: translateY(-10px) rotate(-1deg); + } + 75% { + transform: translateY(-15px) rotate(0.5deg); + } +} + +@keyframes wave-pulse { + 0%, + 100% { + transform: scale(1) rotateZ(0deg); + opacity: 0.3; + } + 25% { + transform: scale(1.05) rotateZ(1deg); + opacity: 0.4; + } + 50% { + transform: scale(0.95) rotateZ(-1deg); + opacity: 0.5; + } + 75% { + transform: scale(1.02) rotateZ(0.5deg); + opacity: 0.35; + } +} + +.animate-wave-float { + animation: wave-float 8s ease-in-out infinite; +} + +.animate-wave-float-delayed { + animation: wave-float 10s ease-in-out infinite 2s; +} + +.animate-wave-float-slow { + animation: wave-float 12s ease-in-out infinite 4s; +} + +.animate-wave-pulse { + animation: wave-pulse 6s ease-in-out infinite; +} + +.animate-wave-pulse-delayed { + animation: wave-pulse 8s ease-in-out infinite 3s; +} + +.animate-wave-pulse-slow { + animation: wave-pulse 10s ease-in-out infinite 1s; +} + +/* Moving Animations */ +@keyframes move-right-slow { + 0% { + transform: translateX(-100px); + } + 100% { + transform: translateX(calc(100vw + 100px)); + } +} + +@keyframes move-right-medium { + 0% { + transform: translateX(-80px); + } + 100% { + transform: translateX(calc(100vw + 80px)); + } +} + +@keyframes move-right-fast { + 0% { + transform: translateX(-120px); + } + 100% { + transform: translateX(calc(100vw + 120px)); + } +} + +@keyframes move-down-slow { + 0% { + transform: translateY(-100px); + } + 100% { + transform: translateY(calc(100vh + 100px)); + } +} + +@keyframes move-down-medium { + 0% { + transform: translateY(-80px); + } + 100% { + transform: translateY(calc(100vh + 80px)); + } +} + +@keyframes move-diagonal-1 { + 0% { + transform: translate(-100px, -100px); + } + 100% { + transform: translate(calc(100vw + 100px), calc(100vh + 100px)); + } +} + +@keyframes move-diagonal-2 { + 0% { + transform: translate(-80px, -50px); + } + 100% { + transform: translate(calc(100vw + 80px), calc(100vh + 50px)); + } +} + +@keyframes move-diagonal-3 { + 0% { + transform: translate(-60px, -80px); + } + 100% { + transform: translate(calc(100vw + 60px), calc(100vh + 80px)); + } +} + +@keyframes orbit-1 { + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(150px) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(150px) rotate(-360deg); + } +} + +@keyframes orbit-2 { + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(200px) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(-360deg) translateX(200px) rotate(360deg); + } +} + +@keyframes orbit-3 { + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(100px) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(100px) rotate(-360deg); + } +} + +/* Gentle Animations */ +@keyframes spin-slow { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes spin-reverse { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } +} + +@keyframes bounce-gentle { + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } +} + +@keyframes bounce-soft { + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-8px); + } +} + +@keyframes pulse-gentle { + 0%, + 100% { + transform: scale(1); + opacity: 0.4; + } + 50% { + transform: scale(1.1); + opacity: 0.6; + } +} + +@keyframes wiggle { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(3deg); + } + 75% { + transform: rotate(-3deg); + } +} + +@keyframes float-gentle { + 0%, + 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-5px); + } +} + +@keyframes scale-gentle { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +@keyframes rotate-gentle { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(180deg); + } +} + +@keyframes sway { + 0%, + 100% { + transform: translateX(0px); + } + 50% { + transform: translateX(10px); + } +} + +/* Animation Classes */ +.animate-move-right-slow { + animation: move-right-slow 25s linear infinite; +} +.animate-move-right-medium { + animation: move-right-medium 20s linear infinite; +} +.animate-move-right-fast { + animation: move-right-fast 15s linear infinite; +} +.animate-move-down-slow { + animation: move-down-slow 30s linear infinite; +} +.animate-move-down-medium { + animation: move-down-medium 25s linear infinite; +} +.animate-move-diagonal-1 { + animation: move-diagonal-1 35s linear infinite; +} +.animate-move-diagonal-2 { + animation: move-diagonal-2 28s linear infinite; +} +.animate-move-diagonal-3 { + animation: move-diagonal-3 32s linear infinite; +} +.animate-orbit-1 { + animation: orbit-1 20s linear infinite; +} +.animate-orbit-2 { + animation: orbit-2 25s linear infinite reverse; +} +.animate-orbit-3 { + animation: orbit-3 15s linear infinite; +} +.animate-spin-slow { + animation: spin-slow 8s linear infinite; +} +.animate-spin-reverse { + animation: spin-reverse 6s linear infinite; +} +.animate-bounce-gentle { + animation: bounce-gentle 3s ease-in-out infinite; +} +.animate-bounce-soft { + animation: bounce-soft 4s ease-in-out infinite; +} +.animate-pulse-gentle { + animation: pulse-gentle 4s ease-in-out infinite; +} +.animate-wiggle { + animation: wiggle 2s ease-in-out infinite; +} +.animate-float-gentle { + animation: float-gentle 5s ease-in-out infinite; +} +.animate-scale-gentle { + animation: scale-gentle 6s ease-in-out infinite; +} +.animate-rotate-gentle { + animation: rotate-gentle 8s ease-in-out infinite; +} +.animate-sway { + animation: sway 3s ease-in-out infinite; +} + +/* Enhanced Animations */ +@keyframes orbit-4 { + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(250px) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(360deg) translateX(250px) rotate(-360deg); + } +} + +@keyframes orbit-5 { + 0% { + transform: translate(-50%, -50%) rotate(0deg) translateX(120px) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(-360deg) translateX(120px) rotate(360deg); + } +} + +@keyframes zigzag-1 { + 0% { + transform: translateX(-100px) translateY(0px); + } + 25% { + transform: translateX(25vw) translateY(-50px); + } + 50% { + transform: translateX(50vw) translateY(50px); + } + 75% { + transform: translateX(75vw) translateY(-30px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(20px); + } +} + +@keyframes zigzag-2 { + 0% { + transform: translateX(-80px) translateY(0px); + } + 20% { + transform: translateX(20vw) translateY(40px); + } + 40% { + transform: translateX(40vw) translateY(-60px); + } + 60% { + transform: translateX(60vw) translateY(30px); + } + 80% { + transform: translateX(80vw) translateY(-40px); + } + 100% { + transform: translateX(calc(100vw + 80px)) translateY(0px); + } +} + +@keyframes zigzag-3 { + 0% { + transform: translateX(-120px) translateY(0px); + } + 16% { + transform: translateX(16vw) translateY(-70px); + } + 33% { + transform: translateX(33vw) translateY(80px); + } + 50% { + transform: translateX(50vw) translateY(-50px); + } + 66% { + transform: translateX(66vw) translateY(60px); + } + 83% { + transform: translateX(83vw) translateY(-40px); + } + 100% { + transform: translateX(calc(100vw + 120px)) translateY(0px); + } +} + +@keyframes spiral-1 { + 0% { + transform: translate(0px, 0px) rotate(0deg) scale(0.5); + } + 25% { + transform: translate(25vw, 25vh) rotate(90deg) scale(1); + } + 50% { + transform: translate(50vw, 50vh) rotate(180deg) scale(1.5); + } + 75% { + transform: translate(75vw, 75vh) rotate(270deg) scale(1); + } + 100% { + transform: translate(100vw, 100vh) rotate(360deg) scale(0.5); + } +} + +@keyframes spiral-2 { + 0% { + transform: translate(0px, 0px) rotate(0deg) scale(1.5); + } + 25% { + transform: translate(-25vw, 25vh) rotate(-90deg) scale(0.8); + } + 50% { + transform: translate(-50vw, 50vh) rotate(-180deg) scale(0.5); + } + 75% { + transform: translate(-75vw, 75vh) rotate(-270deg) scale(1.2); + } + 100% { + transform: translate(-100vw, 100vh) rotate(-360deg) scale(1.5); + } +} + +@keyframes float-random-1 { + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 25% { + transform: translate(50px, -30px) rotate(45deg); + } + 50% { + transform: translate(-30px, 40px) rotate(-30deg); + } + 75% { + transform: translate(40px, 20px) rotate(60deg); + } +} + +@keyframes float-random-2 { + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 20% { + transform: translate(-40px, -50px) rotate(-45deg); + } + 40% { + transform: translate(60px, -20px) rotate(90deg); + } + 60% { + transform: translate(-20px, 60px) rotate(-60deg); + } + 80% { + transform: translate(30px, -40px) rotate(120deg); + } +} + +@keyframes float-random-3 { + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 33% { + transform: translate(70px, 30px) rotate(180deg); + } + 66% { + transform: translate(-50px, -40px) rotate(-90deg); + } +} + +@keyframes float-random-4 { + 0%, + 100% { + transform: translate(0px, 0px) rotate(0deg); + } + 25% { + transform: translate(-60px, 50px) rotate(270deg); + } + 50% { + transform: translate(80px, -30px) rotate(180deg); + } + 75% { + transform: translate(-40px, -60px) rotate(90deg); + } +} + +@keyframes wave-1 { + 0% { + transform: translateX(-100px) translateY(0px); + } + 25% { + transform: translateX(25vw) translateY(-80px); + } + 50% { + transform: translateX(50vw) translateY(0px); + } + 75% { + transform: translateX(75vw) translateY(80px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } +} + +@keyframes wave-2 { + 0% { + transform: translateX(-100px) translateY(0px); + } + 20% { + transform: translateX(20vw) translateY(60px); + } + 40% { + transform: translateX(40vw) translateY(-60px); + } + 60% { + transform: translateX(60vw) translateY(60px); + } + 80% { + transform: translateX(80vw) translateY(-60px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } +} + +@keyframes wave-3 { + 0% { + transform: translateX(-100px) translateY(0px); + } + 33% { + transform: translateX(33vw) translateY(-100px); + } + 66% { + transform: translateX(66vw) translateY(100px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } +} + +@keyframes wave-4 { + 0% { + transform: translateX(-100px) translateY(0px); + } + 16% { + transform: translateX(16vw) translateY(40px); + } + 33% { + transform: translateX(33vw) translateY(-80px); + } + 50% { + transform: translateX(50vw) translateY(40px); + } + 66% { + transform: translateX(66vw) translateY(-80px); + } + 83% { + transform: translateX(83vw) translateY(40px); + } + 100% { + transform: translateX(calc(100vw + 100px)) translateY(0px); + } +} + +@keyframes corner-shoot-1 { + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(100vw, 100vh) rotate(720deg); + } +} + +@keyframes corner-shoot-2 { + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(-100vw, 100vh) rotate(-720deg); + } +} + +@keyframes corner-shoot-3 { + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(100vw, -100vh) rotate(720deg); + } +} + +@keyframes corner-shoot-4 { + 0% { + transform: translate(0px, 0px) rotate(0deg); + } + 100% { + transform: translate(-100vw, -100vh) rotate(-720deg); + } +} + +@keyframes bounce-ball-1 { + 0%, + 100% { + transform: translate(0px, 0px); + } + 25% { + transform: translate(200px, -150px); + } + 50% { + transform: translate(400px, 0px); + } + 75% { + transform: translate(600px, -100px); + } +} + +@keyframes bounce-ball-2 { + 0%, + 100% { + transform: translate(0px, 0px); + } + 33% { + transform: translate(-300px, -200px); + } + 66% { + transform: translate(-600px, 0px); + } +} + +@keyframes bounce-ball-3 { + 0%, + 100% { + transform: translate(0px, 0px); + } + 20% { + transform: translate(150px, -100px); + } + 40% { + transform: translate(300px, 50px); + } + 60% { + transform: translate(150px, -80px); + } + 80% { + transform: translate(-150px, 30px); + } +} + +/* Crazy Animations */ +@keyframes spin-fast { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(720deg); + } +} +@keyframes pulse-fast { + 0%, + 100% { + transform: scale(0.8); + opacity: 0.3; + } + 50% { + transform: scale(1.3); + opacity: 0.8; + } +} +@keyframes wobble { + 0%, + 100% { + transform: rotate(0deg) scale(1); + } + 25% { + transform: rotate(5deg) scale(1.1); + } + 50% { + transform: rotate(-5deg) scale(0.9); + } + 75% { + transform: rotate(3deg) scale(1.05); + } +} +@keyframes shake { + 0%, + 100% { + transform: translateX(0px); + } + 25% { + transform: translateX(-10px); + } + 75% { + transform: translateX(10px); + } +} +@keyframes bounce-crazy { + 0%, + 100% { + transform: translateY(0px) scale(1); + } + 50% { + transform: translateY(-50px) scale(1.2); + } +} +@keyframes spin-wobble { + 0% { + transform: rotate(0deg) scale(1); + } + 25% { + transform: rotate(90deg) scale(1.1); + } + 50% { + transform: rotate(180deg) scale(0.9); + } + 75% { + transform: rotate(270deg) scale(1.05); + } + 100% { + transform: rotate(360deg) scale(1); + } +} +@keyframes flip { + 0% { + transform: rotateY(0deg); + } + 50% { + transform: rotateY(180deg); + } + 100% { + transform: rotateY(360deg); + } +} +@keyframes twirl { + 0% { + transform: rotate(0deg) translateX(0px); + } + 25% { + transform: rotate(90deg) translateX(20px); + } + 50% { + transform: rotate(180deg) translateX(0px); + } + 75% { + transform: rotate(270deg) translateX(-20px); + } + 100% { + transform: rotate(360deg) translateX(0px); + } +} +@keyframes dance { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 25% { + transform: translateY(-20px) rotate(10deg); + } + 50% { + transform: translateY(10px) rotate(-5deg); + } + 75% { + transform: translateY(-15px) rotate(8deg); + } +} +@keyframes jiggle { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(2deg) translateX(2px); + } + 50% { + transform: rotate(-2deg) translateX(-2px); + } + 75% { + transform: rotate(1deg) translateX(1px); + } +} +@keyframes vibrate { + 0%, + 100% { + transform: translate(0px, 0px); + } + 25% { + transform: translate(2px, -2px); + } + 50% { + transform: translate(-2px, 2px); + } + 75% { + transform: translate(2px, 2px); + } +} +@keyframes swing { + 0%, + 100% { + transform: rotate(0deg); + } + 25% { + transform: rotate(15deg); + } + 75% { + transform: rotate(-15deg); + } +} +@keyframes pendulum { + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(30deg); + } +} +@keyframes elastic { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.3) rotate(180deg); + } +} +@keyframes rubber { + 0%, + 100% { + transform: scaleX(1) scaleY(1); + } + 25% { + transform: scaleX(1.2) scaleY(0.8); + } + 75% { + transform: scaleX(0.8) scaleY(1.2); + } +} +@keyframes rocket { + 0% { + transform: scale(0.5) rotate(0deg); + } + 100% { + transform: scale(2) rotate(360deg); + } +} +@keyframes comet { + 0% { + transform: scale(1) rotate(0deg); + opacity: 1; + } + 100% { + transform: scale(0.2) rotate(720deg); + opacity: 0.2; + } +} +@keyframes meteor { + 0% { + transform: scale(0.2) rotate(0deg); + opacity: 0.2; + } + 100% { + transform: scale(1.5) rotate(-720deg); + opacity: 1; + } +} +@keyframes blast { + 0%, + 100% { + transform: scale(1) rotate(0deg); + } + 50% { + transform: scale(2) rotate(180deg); + } +} +@keyframes spin-bounce { + 0%, + 100% { + transform: rotate(0deg) translateY(0px); + } + 50% { + transform: rotate(180deg) translateY(-30px); + } +} +@keyframes flip-bounce { + 0%, + 100% { + transform: rotateX(0deg) translateY(0px); + } + 50% { + transform: rotateX(180deg) translateY(-25px); + } +} +@keyframes scale-bounce { + 0%, + 100% { + transform: scale(1) translateY(0px); + } + 50% { + transform: scale(1.5) translateY(-40px); + } +} + +/* New Animation Classes */ +.animate-orbit-4 { + animation: orbit-4 18s linear infinite; +} +.animate-orbit-5 { + animation: orbit-5 22s linear infinite reverse; +} +.animate-zigzag-1 { + animation: zigzag-1 18s linear infinite; +} +.animate-zigzag-2 { + animation: zigzag-2 22s linear infinite; +} +.animate-zigzag-3 { + animation: zigzag-3 16s linear infinite; +} +.animate-spiral-1 { + animation: spiral-1 30s linear infinite; +} +.animate-spiral-2 { + animation: spiral-2 25s linear infinite; +} +.animate-float-random-1 { + animation: float-random-1 8s ease-in-out infinite; +} +.animate-float-random-2 { + animation: float-random-2 10s ease-in-out infinite; +} +.animate-float-random-3 { + animation: float-random-3 12s ease-in-out infinite; +} +.animate-float-random-4 { + animation: float-random-4 9s ease-in-out infinite; +} +.animate-wave-1 { + animation: wave-1 20s linear infinite; +} +.animate-wave-2 { + animation: wave-2 24s linear infinite; +} +.animate-wave-3 { + animation: wave-3 18s linear infinite; +} +.animate-wave-4 { + animation: wave-4 26s linear infinite; +} +.animate-corner-shoot-1 { + animation: corner-shoot-1 15s linear infinite; +} +.animate-corner-shoot-2 { + animation: corner-shoot-2 18s linear infinite; +} +.animate-corner-shoot-3 { + animation: corner-shoot-3 20s linear infinite; +} +.animate-corner-shoot-4 { + animation: corner-shoot-4 16s linear infinite; +} +.animate-bounce-ball-1 { + animation: bounce-ball-1 12s ease-in-out infinite; +} +.animate-bounce-ball-2 { + animation: bounce-ball-2 14s ease-in-out infinite; +} +.animate-bounce-ball-3 { + animation: bounce-ball-3 10s ease-in-out infinite; +} +.animate-spin-fast { + animation: spin-fast 2s linear infinite; +} +.animate-pulse-fast { + animation: pulse-fast 1.5s ease-in-out infinite; +} +.animate-wobble { + animation: wobble 2s ease-in-out infinite; +} +.animate-shake { + animation: shake 0.5s ease-in-out infinite; +} +.animate-bounce-crazy { + animation: bounce-crazy 1s ease-in-out infinite; +} +.animate-spin-wobble { + animation: spin-wobble 4s ease-in-out infinite; +} +.animate-flip { + animation: flip 3s ease-in-out infinite; +} +.animate-twirl { + animation: twirl 5s ease-in-out infinite; +} +.animate-dance { + animation: dance 3s ease-in-out infinite; +} +.animate-jiggle { + animation: jiggle 1s ease-in-out infinite; +} +.animate-vibrate { + animation: vibrate 0.3s ease-in-out infinite; +} +.animate-swing { + animation: swing 4s ease-in-out infinite; +} +.animate-pendulum { + animation: pendulum 6s ease-in-out infinite; +} +.animate-elastic { + animation: elastic 4s ease-in-out infinite; +} +.animate-rubber { + animation: rubber 2s ease-in-out infinite; +} +.animate-rocket { + animation: rocket 8s ease-in-out infinite; +} +.animate-comet { + animation: comet 12s ease-in-out infinite; +} +.animate-meteor { + animation: meteor 10s ease-in-out infinite; +} +.animate-blast { + animation: blast 3s ease-in-out infinite; +} +.animate-spin-bounce { + animation: spin-bounce 4s ease-in-out infinite; +} +.animate-flip-bounce { + animation: flip-bounce 5s ease-in-out infinite; +} +.animate-scale-bounce { + animation: scale-bounce 3s ease-in-out infinite; +} + +/* Animated Border Light */ +@keyframes border-light { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.animate-border-light { + position: relative; + border-radius: 1rem; +} + +.animate-border-light::before { + content: ""; + position: absolute; + inset: -2px; + background: conic-gradient( + from 0deg, + transparent 0deg, + transparent 270deg, + rgba(168, 85, 247, 0.8) 300deg, + rgba(147, 51, 234, 1) 330deg, + rgba(168, 85, 247, 0.8) 360deg, + transparent 30deg, + transparent 360deg + ); + border-radius: inherit; + animation: border-light 3s linear infinite; + z-index: -1; +} + +.animate-border-light::after { + content: ""; + position: absolute; + inset: 2px; + background: inherit; + border-radius: inherit; + z-index: -1; +} diff --git a/apps/clients/src/main.tsx b/apps/clients/src/main.tsx new file mode 100644 index 0000000..70ff399 --- /dev/null +++ b/apps/clients/src/main.tsx @@ -0,0 +1,28 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "@xtablo/shared"; +import { SessionProvider } from "@xtablo/shared/contexts/SessionContext"; +import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext"; +import { Toaster } from "@xtablo/ui/components/sonner"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter as Router } from "react-router-dom"; +import App from "./App"; + +import "@xtablo/ui/styles/globals.css"; +import "./main.css"; +import "./i18n"; + +createRoot(document.getElementById("client-root")!).render( + + + + + + + + + + + + +); diff --git a/apps/clients/src/routes.tsx b/apps/clients/src/routes.tsx new file mode 100644 index 0000000..4f94f7f --- /dev/null +++ b/apps/clients/src/routes.tsx @@ -0,0 +1,11 @@ +import { Route, Routes } from "react-router-dom"; + +export default function AppRoutes() { + return ( + + Auth callback placeholder} /> + Tablo view placeholder} /> + Client portal placeholder} /> + + ); +} diff --git a/apps/clients/tsconfig.json b/apps/clients/tsconfig.json new file mode 100644 index 0000000..64a1401 --- /dev/null +++ b/apps/clients/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@xtablo/ui": ["../../packages/ui/src"], + "@xtablo/ui/*": ["../../packages/ui/src/*"], + "@xtablo/shared": ["../../packages/shared/src"], + "@xtablo/shared/*": ["../../packages/shared/src/*"], + "@xtablo/tablo-views": ["../../packages/tablo-views/src"], + "@xtablo/tablo-views/*": ["../../packages/tablo-views/src/*"] + } + }, + "include": ["src"], + "references": [] +} diff --git a/apps/clients/vite.config.ts b/apps/clients/vite.config.ts new file mode 100644 index 0000000..f9f5138 --- /dev/null +++ b/apps/clients/vite.config.ts @@ -0,0 +1,22 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig, type PluginOption } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig(({ mode }) => { + const plugins: PluginOption[] = [ + react(), + tailwindcss(), + tsconfigPaths(), + ]; + + if (mode !== "test" && process.env.VITEST !== "true") { + plugins.push(cloudflare()); + } + + return { + plugins, + server: { cors: false }, + }; +}); diff --git a/apps/clients/worker/index.ts b/apps/clients/worker/index.ts new file mode 100644 index 0000000..0dcbb86 --- /dev/null +++ b/apps/clients/worker/index.ts @@ -0,0 +1,9 @@ +export default { + fetch(request: Request) { + const url = new URL(request.url); + if (url.pathname.startsWith("/api/")) { + return Response.json({ name: "Cloudflare" }); + } + return new Response(null, { status: 404 }); + }, +}; diff --git a/apps/clients/wrangler.toml b/apps/clients/wrangler.toml new file mode 100644 index 0000000..13aff9a --- /dev/null +++ b/apps/clients/wrangler.toml @@ -0,0 +1,16 @@ +name = "xtablo-clients" +main = "worker/index.ts" +compatibility_date = "2025-07-09" + +[assets] +directory = "./dist/" +not_found_handling = "single-page-application" + +[observability] +enabled = true + +[env.staging] +route = { pattern = "clients-staging.xtablo.com", custom_domain = true } + +[env.production] +route = { pattern = "clients.xtablo.com", custom_domain = true } diff --git a/package.json b/package.json index 50b96ef..bf99826 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dev": "turbo dev", "dev:main": "turbo dev --filter=@xtablo/main", "dev:external": "turbo dev --filter=@xtablo/external", + "dev:clients": "turbo dev --filter=@xtablo/clients", "dev:api": "turbo dev --filter=@xtablo/api", "deploy:main:staging": "turbo deploy:staging --filter=@xtablo/main", "deploy:main:prod": "turbo deploy:prod --filter=@xtablo/main",