Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fca7b28
docs: add web-vue mirror context and glossary
xirothedev Aug 31, 2026
c11b5c7
feat(web-vue): scaffold Vite+Vue3 SPA with Tailwind v4, router, vue-q…
xirothedev Aug 31, 2026
a628265
feat(web-vue): scaffold Vite+Vue3+Tailwind v4 app (#40)
xirothedev Aug 31, 2026
0a348b5
feat(web-vue): port 12 ui components to Vue (reka-ui/motion-v/chart.j…
xirothedev Aug 31, 2026
ed6caf7
feat(web-vue): port components/ui to shadcn-vue (#41)
xirothedev Aug 31, 2026
0086eef
feat(web-vue): app shell — port lib/composables/router with all 42 st…
xirothedev Aug 31, 2026
80d4110
feat(web-vue): app shell, vue-query hooks, router with all 42 stubs (…
xirothedev Aug 31, 2026
c56dd81
feat(web-vue): T9 calendar page with FullCalendar (daygrid/timegrid),…
xirothedev Aug 31, 2026
5b78682
feat(web-vue): blog slice — /blog list + /blog/[slug] post (T8 #47)
xirothedev Aug 31, 2026
df5fe13
feat(web-vue): auth pages — login, signup, forgot/reset password, ver…
xirothedev Aug 31, 2026
b912aa3
chore(web-vue): drop unused auth-loading (no Suspense fallback needed)
xirothedev Aug 31, 2026
40e334d
feat(web-vue): shop, cart, checkout pages + cart drawer (T5, #44)
xirothedev Aug 31, 2026
7817669
feat(web-vue): T4 marketing + legal pages — home, about, generation, …
xirothedev Sep 1, 2026
68424a1
feat(web-vue): t4 slice (#43
xirothedev Sep 1, 2026
bcf68ee
feat(web-vue): t5 slice (#44
xirothedev Sep 1, 2026
9e9dae6
feat(web-vue): t6 slice (#45
xirothedev Sep 1, 2026
8f583c5
feat(web-vue): t8 slice (#47)
xirothedev Sep 1, 2026
0a1d26b
feat(web-vue): t9 slice (#48)
xirothedev Sep 1, 2026
210329e
feat(web-vue): orders + account pages (T7 #46)
xirothedev Sep 1, 2026
9324542
feat(web-vue): admin pages — dashboard, products, users, orders, tran…
xirothedev Sep 1, 2026
73b5f36
feat(web-vue): orders + account pages (#46)
xirothedev Sep 1, 2026
1ce45d8
feat(web-vue): admin pages (#49)
xirothedev Sep 1, 2026
d85cc47
feat(web-vue): payments return/cancel/[orderId] pages mirroring apps/web
xirothedev Sep 1, 2026
5f7d54b
feat(web-vue): payments pages (#50)
xirothedev Sep 1, 2026
00e271f
fix(web-vue): drop dead csrf getter, single public-route matcher in a…
xirothedev Sep 1, 2026
26a5618
fix(web-vue): lazy route chunks, route-meta navbar variant, no site c…
xirothedev Sep 1, 2026
e94cb53
fix(web-vue): port real animated 404 page from apps/web
xirothedev Sep 1, 2026
d44aaee
fix(web-vue): port Hero/TrustSection/FeaturesGrid and wire /shop index
xirothedev Sep 1, 2026
3a71d8c
fix(web-vue): shared vi-VN date helpers replace per-component Intl co…
xirothedev Sep 1, 2026
2489e44
fix(web-vue): single availableStock computed in product page
xirothedev Sep 1, 2026
6ee4ad9
fix(web-vue): expose mock-data fallback flag on events query, notice …
xirothedev Sep 1, 2026
e75ea9b
refactor(web-vue): move use-admin/use-blog hooks into lib/api/hooks
xirothedev Sep 1, 2026
cb16328
refactor(web-vue): shared admin table wrapper (columns+visibility+Dat…
xirothedev Sep 1, 2026
3fbcff3
fix(web-vue): review findings + integration gaps (PR #37 review)
xirothedev Sep 1, 2026
77db7a9
chore(web-vue): bun test script
xirothedev Sep 1, 2026
1252a15
refactor(web-vue): unify relativeTime in lib/date; derive usePageMeta…
xirothedev Sep 1, 2026
44a4aef
refactor(web-vue): own pending-order handshake, centralize cart actio…
xirothedev Sep 1, 2026
ec5d287
refactor(web-vue): deepen auth into src/lib/auth, api-client becomes …
xirothedev Sep 1, 2026
88656a8
refactor(web-vue): drop dead hook exports, admin table interface shrink
xirothedev Sep 1, 2026
6e11c4b
refactor(web-vue): s1-auth
xirothedev Sep 1, 2026
c948dd3
refactor(web-vue): s2-cart-pay
xirothedev Sep 1, 2026
f0ef059
merge s3-admin: keep cart actions, drop dead useClearCart
xirothedev Sep 1, 2026
50e2a67
merge s4-formats: keep cart totals, apply SEO image fallback
xirothedev Sep 1, 2026
e3401b1
fix(web,web-vue): payments API double /v1 prefix (baseURL already inc…
xirothedev Sep 1, 2026
edd9602
fix(web,web-vue): api-client rejects Error instances so server messag…
xirothedev Sep 1, 2026
1296991
refactor(web,web-vue): money formatting unified on formatPrice + ₫ (w…
xirothedev Sep 1, 2026
31d9fbe
style: prettier-format edited files; wire apps/web-vue into format:ch…
xirothedev Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTEXT-MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ This repo has four contexts, one per app. Each context has its own `CONTEXT.md`
- **api-go** — Go port of the API. See `apps/api-go/CONTEXT.md`.
- **api-axum** — Rust Axum port of the API. See `apps/api-axum/CONTEXT.md`.
- **web** — frontend web app. See `apps/web/CONTEXT.md` (created lazily by `/domain-modeling`).
- **web-vue** — Vue mirror of web. See `apps/web-vue/CONTEXT.md`.

System-wide decisions live in `docs/adr/`.
3 changes: 3 additions & 0 deletions apps/web-vue/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_API_URL=http://localhost:4000/v1
VITE_SITE_URL=http://localhost:3001
VITE_CDN_URL=https://cdn.wds.xirothedev.site
2 changes: 2 additions & 0 deletions apps/web-vue/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
11 changes: 11 additions & 0 deletions apps/web-vue/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# CONTEXT — web-vue

## Language

**Mirror**:
A separate app that reproduces another app's pages and behavior against the same API, kept running alongside the original. The original stays authoritative until parity is proven.
_Avoid_: clone, fork, port

**Mirror page**:
One route in the mirror that answers to the same route in `web`, with the same visible structure and behavior. Visual parity may be approximate where no equivalent component library exists (calendar).
_Avoid_: copy, twin
23 changes: 23 additions & 0 deletions apps/web-vue/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Same font as apps/web layout.tsx: Inter({ subsets: ['latin'], weight: ['300','400','500','600'], variable: '--font-inter' }) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap"
rel="stylesheet"
/>
<style>
:root {
--font-inter: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
57 changes: 57 additions & 0 deletions apps/web-vue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "web-vue",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "vue-tsc --noEmit",
"test": "bun test --pass-with-no-tests",
"format": "bunx --bun --no-install prettier --write \"**/*.{js,ts,vue,json,css}\"",
"format:check": "bunx --bun --no-install prettier --check \"**/*.{js,ts,vue,json,css}\""
},
"dependencies": {
"@fullcalendar/core": "^6.1.21",
"@fullcalendar/daygrid": "^6.1.21",
"@fullcalendar/interaction": "^6.1.21",
"@fullcalendar/timegrid": "^6.1.21",
"@fullcalendar/vue3": "^6.1.21",
"@tanstack/vue-query": "^5.102.8",
"@tiptap/extension-code-block": "^3.30.6",
"@tiptap/extension-link": "^3.30.6",
"@tiptap/extension-placeholder": "^3.30.6",
"@tiptap/markdown": "^3.30.6",
"@tiptap/pm": "^3.30.6",
"@tiptap/starter-kit": "^3.30.6",
"@tiptap/vue-3": "^3.30.6",
"@unhead/vue": "^3.4.0",
"@vee-validate/zod": "^4.15.1",
"axios": "^1.20.0",
"chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"countup.js": "^2.10.1",
"lucide-vue-next": "^1.0.0",
"marked": "^18.0.11",
"motion-v": "^2.4.0",
"reka-ui": "^2.10.4",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"unhead": "^3.4.0",
"vee-validate": "^4.15.1",
"vue": "^3.5.42",
"vue-chartjs": "^5.3.4",
"vue-router": "^5.3.0",
"zod": "^4.5.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^26.4.0",
"@vitejs/plugin-vue": "^6.0.8",
"tailwindcss": "^4.3.3",
"typescript": "^5.9.0",
"vite": "^8.2.2",
"vue-tsc": "^3.3.11"
}
}
Binary file added apps/web-vue/public/icons/facebook.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/icons/gmail.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/icons/messenger.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/icons/telephone.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/image/hero-image.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/image/khach-hang.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/image/ton-chi.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/image/uit-school.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/web-vue/public/image/wds-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/shop/ao-thun.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/shop/day-deo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/shop/moc-khoa.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web-vue/public/shop/pad-chuot.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions apps/web-vue/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { useHead } from '@unhead/vue';
import { computed } from 'vue';
import { useRoute } from 'vue-router';

import CsrfInitializer from '@/components/CsrfInitializer.vue';
import Footer from '@/components/Footer.vue';
import Navbar from '@/components/Navbar.vue';
import FloatingCartButton from '@/components/shop/floating-cart-button.vue';
import { provideAuth } from '@/composables/use-auth';
import { useCartDrawer } from '@/composables/use-cart-drawer';
import { defaultMetadata } from '@/lib/metadata';
import { getOrganizationSchema, getWebSiteSchema } from '@/lib/structured-data';

const route = useRoute();
// chrome: 'none' routes (auth/admin) render their own chrome, like apps/web page-level layouts
const bare = computed(() => route.meta.chrome === 'none');

provideAuth();
// cart drawer state is provided app-wide; the drawer UI itself lands with the shop ticket
useCartDrawer();

// Root layout head (apps/web layout.tsx metadata) + StructuredData.tsx JSON-LD scripts.
// Pages override title/description via usePageMeta (child head wins over app head).
useHead({
...defaultMetadata,
htmlAttrs: { lang: 'vi' },
script: [
{ type: 'application/ld+json', innerHTML: JSON.stringify(getOrganizationSchema()) },
{ type: 'application/ld+json', innerHTML: JSON.stringify(getWebSiteSchema()) },
],
});
</script>

<template>
<CsrfInitializer />
<Navbar v-if="!bare" :variant="route.meta.navbarVariant ?? 'dark'" />
<main :class="bare ? '' : 'bg-background text-foreground min-h-screen pt-14 md:pt-16'">
<RouterView />
</main>
<FloatingCartButton />
<Footer v-if="!bare" />
</template>
Loading
Loading