From cb65e5e6015f8dc64ac25c9c4237f17f38c27e90 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Mon, 18 May 2026 20:18:28 +0330 Subject: [PATCH] start frant 050228 --- .env.admin | 2 + .env.example | 3 +- .gitignore | 1 + package-lock.json | 683 ++++++++++++++++++++++++ package.json | 8 +- src/App.vue | 4 +- src/api/client.ts | 9 +- src/components/shell/AppShell.vue | 7 + src/components/ui/AppButton.vue | 35 ++ src/components/ui/AppInput.vue | 26 + src/components/ui/ThemeToggle.vue | 19 + src/composables/useAppMode.ts | 20 + src/composables/useTheme.ts | 21 + src/layouts/AdminLayout.vue | 67 +++ src/layouts/AuthLayout.vue | 24 + src/layouts/WebAppLayout.vue | 84 +++ src/router/index.ts | 124 ++++- src/stores/auth.ts | 8 +- src/styles/main.css | 199 ++----- src/views/HomeView.vue | 40 -- src/views/LoginView.vue | 59 -- src/views/VerifyOtpView.vue | 127 ----- src/views/admin/AdminBusinessesView.vue | 12 + src/views/admin/AdminHomeView.vue | 20 + src/views/admin/AdminUsersView.vue | 12 + src/views/app/AppBookingsView.vue | 12 + src/views/app/AppHomeView.vue | 19 + src/views/app/AppOwnerView.vue | 13 + src/views/app/AppProfileView.vue | 38 ++ src/views/auth/LoginView.vue | 70 +++ src/views/auth/VerifyOtpView.vue | 139 +++++ src/vite-env.d.ts | 1 + vite.config.ts | 12 +- 33 files changed, 1493 insertions(+), 425 deletions(-) create mode 100644 .env.admin create mode 100644 src/components/shell/AppShell.vue create mode 100644 src/components/ui/AppButton.vue create mode 100644 src/components/ui/AppInput.vue create mode 100644 src/components/ui/ThemeToggle.vue create mode 100644 src/composables/useAppMode.ts create mode 100644 src/composables/useTheme.ts create mode 100644 src/layouts/AdminLayout.vue create mode 100644 src/layouts/AuthLayout.vue create mode 100644 src/layouts/WebAppLayout.vue delete mode 100644 src/views/HomeView.vue delete mode 100644 src/views/LoginView.vue delete mode 100644 src/views/VerifyOtpView.vue create mode 100644 src/views/admin/AdminBusinessesView.vue create mode 100644 src/views/admin/AdminHomeView.vue create mode 100644 src/views/admin/AdminUsersView.vue create mode 100644 src/views/app/AppBookingsView.vue create mode 100644 src/views/app/AppHomeView.vue create mode 100644 src/views/app/AppOwnerView.vue create mode 100644 src/views/app/AppProfileView.vue create mode 100644 src/views/auth/LoginView.vue create mode 100644 src/views/auth/VerifyOtpView.vue diff --git a/.env.admin b/.env.admin new file mode 100644 index 0000000..0be572a --- /dev/null +++ b/.env.admin @@ -0,0 +1,2 @@ +VITE_API_URL=https://api.montime.ir +VITE_APP_MODE=admin diff --git a/.env.example b/.env.example index fd5124e..e5af2a6 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ -# API base URL (no trailing slash) VITE_API_URL=http://localhost:3000 +# web = montime.ir | admin = adm.montime.ir (also auto-detected from hostname) +VITE_APP_MODE=web diff --git a/.gitignore b/.gitignore index 633f97a..cd6a76f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules dist +dist-admin .env .env.local *.log diff --git a/package-lock.json b/package-lock.json index 479c5a0..d8c7a8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,15 @@ "name": "montime-web", "version": "0.0.1", "dependencies": { + "@vueuse/core": "^12.8.2", "pinia": "^2.3.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.7", "@vitejs/plugin-vue": "^5.2.1", + "tailwindcss": "^4.1.7", "typescript": "~5.7.3", "vite": "^6.0.7", "vue-tsc": "^2.2.0" @@ -507,12 +510,55 @@ "node": ">=18" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://mirror-npm.runflare.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://mirror-npm.runflare.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://mirror-npm.runflare.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://mirror-npm.runflare.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://mirror-npm.runflare.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://mirror-npm.runflare.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -863,6 +909,278 @@ "win32" ] }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://mirror-npm.runflare.com/@types/estree/-/estree-1.0.8.tgz", @@ -870,6 +1188,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://mirror-npm.runflare.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://mirror-npm.runflare.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1055,6 +1379,42 @@ "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", "license": "MIT" }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://mirror-npm.runflare.com/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://mirror-npm.runflare.com/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://mirror-npm.runflare.com/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/alien-signals": { "version": "1.0.13", "resolved": "https://mirror-npm.runflare.com/alien-signals/-/alien-signals-1.0.13.tgz", @@ -1092,6 +1452,30 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://mirror-npm.runflare.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.3", + "resolved": "https://mirror-npm.runflare.com/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://mirror-npm.runflare.com/entities/-/entities-7.0.1.tgz", @@ -1185,6 +1569,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://mirror-npm.runflare.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://mirror-npm.runflare.com/he/-/he-1.2.0.tgz", @@ -1195,6 +1586,277 @@ "he": "bin/he" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://mirror-npm.runflare.com/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://mirror-npm.runflare.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://mirror-npm.runflare.com/magic-string/-/magic-string-0.30.21.tgz", @@ -1375,6 +2037,27 @@ "node": ">=0.10.0" } }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://mirror-npm.runflare.com/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://mirror-npm.runflare.com/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://mirror-npm.runflare.com/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/package.json b/package.json index 6bedd4b..3b3571e 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,22 @@ "type": "module", "scripts": { "dev": "vite", + "dev:admin": "vite --port 5174 --mode admin", "build": "vite build", - "preview": "vite preview" + "build:admin": "vite build --mode admin", + "preview": "vite preview", + "preview:admin": "vite preview --port 5174 --mode admin" }, "dependencies": { + "@vueuse/core": "^12.8.2", "pinia": "^2.3.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.7", "@vitejs/plugin-vue": "^5.2.1", + "tailwindcss": "^4.1.7", "typescript": "~5.7.3", "vite": "^6.0.7", "vue-tsc": "^2.2.0" diff --git a/src/App.vue b/src/App.vue index 8d0e1cf..39f5614 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,5 @@ diff --git a/src/api/client.ts b/src/api/client.ts index 77c9a94..ab98daf 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -14,14 +14,19 @@ type ApiOptions = RequestInit & { token?: string; }; +export function getApiToken(): string | null { + return localStorage.getItem('montime_access_token'); +} + export async function api(path: string, options: ApiOptions = {}): Promise { + const token = options.token ?? getApiToken() ?? undefined; const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record | undefined), }; - if (options.token) { - headers.Authorization = `Bearer ${options.token}`; + if (token) { + headers.Authorization = `Bearer ${token}`; } const { token: _token, ...fetchOptions } = options; diff --git a/src/components/shell/AppShell.vue b/src/components/shell/AppShell.vue new file mode 100644 index 0000000..87a5754 --- /dev/null +++ b/src/components/shell/AppShell.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/ui/AppButton.vue b/src/components/ui/AppButton.vue new file mode 100644 index 0000000..57e68d0 --- /dev/null +++ b/src/components/ui/AppButton.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/ui/AppInput.vue b/src/components/ui/AppInput.vue new file mode 100644 index 0000000..2e0074b --- /dev/null +++ b/src/components/ui/AppInput.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/ui/ThemeToggle.vue b/src/components/ui/ThemeToggle.vue new file mode 100644 index 0000000..f2ac2c1 --- /dev/null +++ b/src/components/ui/ThemeToggle.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/composables/useAppMode.ts b/src/composables/useAppMode.ts new file mode 100644 index 0000000..3f42cfd --- /dev/null +++ b/src/composables/useAppMode.ts @@ -0,0 +1,20 @@ +export type AppMode = 'web' | 'admin'; + +/** `web` = montime.ir (customers + business owners). `admin` = adm.montime.ir only. */ +export function getAppMode(): AppMode { + const fromEnv = import.meta.env.VITE_APP_MODE as AppMode | undefined; + if (fromEnv === 'web' || fromEnv === 'admin') { + return fromEnv; + } + if (typeof window !== 'undefined') { + const host = window.location.hostname; + if (host === 'adm.montime.ir' || host.startsWith('adm.')) { + return 'admin'; + } + } + return 'web'; +} + +export function useAppMode() { + return { mode: getAppMode(), isAdmin: getAppMode() === 'admin', isWeb: getAppMode() === 'web' }; +} diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..86fe729 --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,21 @@ +import { useColorMode } from '@vueuse/core'; + +export function useTheme() { + const mode = useColorMode({ + storageKey: 'montime-theme', + attribute: 'class', + modes: { + light: 'light', + dark: 'dark', + }, + }); + + function toggle() { + mode.value = mode.value === 'dark' ? 'light' : 'dark'; + } + + return { + isDark: mode, + toggle, + }; +} diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..1db2758 --- /dev/null +++ b/src/layouts/AdminLayout.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/layouts/AuthLayout.vue b/src/layouts/AuthLayout.vue new file mode 100644 index 0000000..c291864 --- /dev/null +++ b/src/layouts/AuthLayout.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/layouts/WebAppLayout.vue b/src/layouts/WebAppLayout.vue new file mode 100644 index 0000000..449e9ba --- /dev/null +++ b/src/layouts/WebAppLayout.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/router/index.ts b/src/router/index.ts index 4e3fb61..876d0e6 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,47 +1,119 @@ import { createRouter, createWebHistory } from 'vue-router'; +import { getAppMode } from '@/composables/useAppMode'; import { useAuthStore } from '@/stores/auth'; +const mode = getAppMode(); + +const webRoutes = [ + { path: '/', redirect: '/login' }, + { + path: '/login', + name: 'login', + component: () => import('@/views/auth/LoginView.vue'), + meta: { guest: true, shell: 'auth' }, + }, + { + path: '/verify', + name: 'verify', + component: () => import('@/views/auth/VerifyOtpView.vue'), + meta: { guest: true, shell: 'auth' }, + }, + { + path: '/app', + name: 'app-home', + component: () => import('@/views/app/AppHomeView.vue'), + meta: { requiresAuth: true, shell: 'web' }, + }, + { + path: '/app/bookings', + name: 'app-bookings', + component: () => import('@/views/app/AppBookingsView.vue'), + meta: { requiresAuth: true, shell: 'web' }, + }, + { + path: '/app/profile', + name: 'app-profile', + component: () => import('@/views/app/AppProfileView.vue'), + meta: { requiresAuth: true, shell: 'web' }, + }, + { + path: '/app/owner', + name: 'app-owner', + component: () => import('@/views/app/AppOwnerView.vue'), + meta: { requiresAuth: true, requiresBusinessOwner: true, shell: 'web' }, + }, + { path: '/home', redirect: '/app' }, +]; + +const adminRoutes = [ + { path: '/', redirect: '/admin/login' }, + { + path: '/admin/login', + name: 'admin-login', + component: () => import('@/views/auth/LoginView.vue'), + meta: { guest: true, shell: 'auth', adminAuth: true }, + }, + { + path: '/admin/verify', + name: 'admin-verify', + component: () => import('@/views/auth/VerifyOtpView.vue'), + meta: { guest: true, shell: 'auth', adminAuth: true }, + }, + { + path: '/admin', + name: 'admin-home', + component: () => import('@/views/admin/AdminHomeView.vue'), + meta: { requiresAuth: true, requiresAdmin: true, shell: 'admin' }, + }, + { + path: '/admin/businesses', + name: 'admin-businesses', + component: () => import('@/views/admin/AdminBusinessesView.vue'), + meta: { requiresAuth: true, requiresAdmin: true, shell: 'admin' }, + }, + { + path: '/admin/users', + name: 'admin-users', + component: () => import('@/views/admin/AdminUsersView.vue'), + meta: { requiresAuth: true, requiresAdmin: true, shell: 'admin' }, + }, +]; + const router = createRouter({ history: createWebHistory(), - routes: [ - { - path: '/', - redirect: '/login', - }, - { - path: '/login', - name: 'login', - component: () => import('@/views/LoginView.vue'), - meta: { guest: true }, - }, - { - path: '/verify', - name: 'verify', - component: () => import('@/views/VerifyOtpView.vue'), - meta: { guest: true }, - }, - { - path: '/home', - name: 'home', - component: () => import('@/views/HomeView.vue'), - meta: { requiresAuth: true }, - }, - ], + routes: mode === 'admin' ? adminRoutes : webRoutes, }); router.beforeEach(async (to) => { const auth = useAuthStore(); + const appMode = getAppMode(); if (auth.token && !auth.user) { await auth.loadUser(); } if (to.meta.requiresAuth && !auth.isLoggedIn) { - return { name: 'login' }; + return appMode === 'admin' ? { name: 'admin-login' } : { name: 'login' }; + } + + if (to.meta.requiresAdmin && !auth.isAdmin) { + return appMode === 'admin' ? { name: 'admin-login' } : { name: 'login' }; + } + + if (to.meta.requiresBusinessOwner && !auth.isBusinessOwner && !auth.isAdmin) { + return { name: 'app-home' }; } if (to.meta.guest && auth.isLoggedIn) { - return { name: 'home' }; + if (to.meta.adminAuth) { + return auth.isAdmin ? { name: 'admin-home' } : undefined; + } + return { name: 'app-home' }; + } + + if (appMode === 'admin' && auth.isLoggedIn && !auth.isAdmin && !to.meta.guest) { + auth.logout(); + return { name: 'admin-login' }; } }); diff --git a/src/stores/auth.ts b/src/stores/auth.ts index f069b11..75b5cab 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia'; -import { ref, computed } from 'vue'; +import { computed, ref } from 'vue'; import type { User } from '@/api/auth'; import { fetchMe } from '@/api/auth'; @@ -10,6 +10,9 @@ export const useAuthStore = defineStore('auth', () => { const user = ref(null); const isLoggedIn = computed(() => Boolean(token.value)); + const isAdmin = computed(() => user.value?.role === 'admin'); + const isBusinessOwner = computed(() => user.value?.role === 'business_owner'); + const isCustomer = computed(() => user.value?.role === 'customer'); function setSession(accessToken: string, profile: User) { token.value = accessToken; @@ -38,6 +41,9 @@ export const useAuthStore = defineStore('auth', () => { token, user, isLoggedIn, + isAdmin, + isBusinessOwner, + isCustomer, setSession, logout, loadUser, diff --git a/src/styles/main.css b/src/styles/main.css index 0e12407..8fb9173 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1,168 +1,45 @@ -:root { - --bg: #0f1419; - --surface: #1a2332; - --surface-2: #243044; - --text: #f0f4f8; - --muted: #94a3b8; - --accent: #22c55e; - --accent-hover: #16a34a; - --danger: #f87171; - --border: rgba(148, 163, 184, 0.2); - --radius: 12px; - --shadow: 0 16px 48px rgba(0, 0, 0, 0.35); +@import 'tailwindcss'; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --font-sans: 'Vazirmatn', ui-sans-serif, system-ui, sans-serif; + + --color-brand-50: #ecfdf5; + --color-brand-100: #d1fae5; + --color-brand-500: #22c55e; + --color-brand-600: #16a34a; + --color-brand-700: #15803d; + + --color-surface: #ffffff; + --color-surface-2: #f8fafc; + --color-surface-3: #f1f5f9; + --color-border: #e2e8f0; + --color-text: #0f172a; + --color-muted: #64748b; } -*, -*::before, -*::after { - box-sizing: border-box; +@layer theme { + .dark { + --color-surface: #0f1419; + --color-surface-2: #1a2332; + --color-surface-3: #243044; + --color-border: #334155; + --color-text: #f1f5f9; + --color-muted: #94a3b8; + } } -html, -body, -#app { - margin: 0; - min-height: 100%; -} +@layer base { + html { + @apply antialiased; + } -body { - font-family: 'Vazirmatn', system-ui, sans-serif; - background: radial-gradient(ellipse at top, #1e3a2f 0%, var(--bg) 55%); - color: var(--text); - line-height: 1.6; -} + body { + @apply bg-surface-2 text-text font-sans; + } -.app-shell { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - padding: 1.5rem; -} - -.card { - width: 100%; - max-width: 420px; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 2rem; - box-shadow: var(--shadow); -} - -.logo { - font-size: 1.75rem; - font-weight: 700; - margin: 0 0 0.25rem; - letter-spacing: -0.02em; -} - -.subtitle { - color: var(--muted); - margin: 0 0 1.75rem; - font-size: 0.95rem; -} - -.field { - margin-bottom: 1rem; -} - -.field label { - display: block; - font-size: 0.875rem; - font-weight: 500; - margin-bottom: 0.4rem; - color: var(--muted); -} - -.field input { - width: 100%; - padding: 0.75rem 1rem; - border-radius: 8px; - border: 1px solid var(--border); - background: var(--surface-2); - color: var(--text); - font: inherit; - direction: ltr; - text-align: left; -} - -.field input:focus { - outline: 2px solid var(--accent); - outline-offset: 1px; -} - -.btn { - width: 100%; - padding: 0.85rem 1rem; - border: none; - border-radius: 8px; - font: inherit; - font-weight: 600; - cursor: pointer; - transition: background 0.15s, opacity 0.15s; -} - -.btn-primary { - background: var(--accent); - color: #052e16; -} - -.btn-primary:hover:not(:disabled) { - background: var(--accent-hover); -} - -.btn:disabled { - opacity: 0.55; - cursor: not-allowed; -} - -.btn-ghost { - background: transparent; - color: var(--muted); - margin-top: 0.75rem; -} - -.error { - background: rgba(248, 113, 113, 0.12); - border: 1px solid rgba(248, 113, 113, 0.35); - color: var(--danger); - padding: 0.75rem 1rem; - border-radius: 8px; - font-size: 0.875rem; - margin-bottom: 1rem; -} - -.timer { - text-align: center; - color: var(--muted); - font-size: 0.875rem; - margin-top: 1rem; -} - -.home-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; -} - -.user-card { - background: var(--surface-2); - border-radius: 8px; - padding: 1rem; -} - -.user-card dt { - color: var(--muted); - font-size: 0.8rem; -} - -.user-card dd { - margin: 0.25rem 0 0.75rem; - font-weight: 500; -} - -.user-card dd:last-child { - margin-bottom: 0; + #app { + @apply min-h-dvh; + } } diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue deleted file mode 100644 index c67f892..0000000 --- a/src/views/HomeView.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue deleted file mode 100644 index b52af74..0000000 --- a/src/views/LoginView.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/src/views/VerifyOtpView.vue b/src/views/VerifyOtpView.vue deleted file mode 100644 index 822d286..0000000 --- a/src/views/VerifyOtpView.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - diff --git a/src/views/admin/AdminBusinessesView.vue b/src/views/admin/AdminBusinessesView.vue new file mode 100644 index 0000000..e722987 --- /dev/null +++ b/src/views/admin/AdminBusinessesView.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/admin/AdminHomeView.vue b/src/views/admin/AdminHomeView.vue new file mode 100644 index 0000000..384946a --- /dev/null +++ b/src/views/admin/AdminHomeView.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/admin/AdminUsersView.vue b/src/views/admin/AdminUsersView.vue new file mode 100644 index 0000000..6857cc5 --- /dev/null +++ b/src/views/admin/AdminUsersView.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/app/AppBookingsView.vue b/src/views/app/AppBookingsView.vue new file mode 100644 index 0000000..8c7be57 --- /dev/null +++ b/src/views/app/AppBookingsView.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/app/AppHomeView.vue b/src/views/app/AppHomeView.vue new file mode 100644 index 0000000..90d9f44 --- /dev/null +++ b/src/views/app/AppHomeView.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/views/app/AppOwnerView.vue b/src/views/app/AppOwnerView.vue new file mode 100644 index 0000000..2a5f246 --- /dev/null +++ b/src/views/app/AppOwnerView.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/views/app/AppProfileView.vue b/src/views/app/AppProfileView.vue new file mode 100644 index 0000000..15a3686 --- /dev/null +++ b/src/views/app/AppProfileView.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/views/auth/LoginView.vue b/src/views/auth/LoginView.vue new file mode 100644 index 0000000..75285d9 --- /dev/null +++ b/src/views/auth/LoginView.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/views/auth/VerifyOtpView.vue b/src/views/auth/VerifyOtpView.vue new file mode 100644 index 0000000..927a664 --- /dev/null +++ b/src/views/auth/VerifyOtpView.vue @@ -0,0 +1,139 @@ + + + diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 29c29a1..47e0411 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_API_URL: string; + readonly VITE_APP_MODE?: 'web' | 'admin'; } interface ImportMeta { diff --git a/vite.config.ts b/vite.config.ts index 942bc23..fa528df 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,15 +1,19 @@ import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; +import tailwindcss from '@tailwindcss/vite'; import { fileURLToPath, URL } from 'node:url'; -export default defineConfig({ - plugins: [vue()], +export default defineConfig(({ mode }) => ({ + build: { + outDir: mode === 'admin' ? 'dist-admin' : 'dist', + }, + plugins: [vue(), tailwindcss()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, server: { - port: 5173, + port: mode === 'admin' ? 5174 : 5173, }, -}); +}));