diff --git a/package-lock.json b/package-lock.json index 9631a9b..41a8f6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "app", "version": "0.0.0", "dependencies": { + "pinia": "^3.0.4", "vue": "^3.5.24", "vue-router": "^4.6.4" }, @@ -1266,6 +1267,30 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.27", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", @@ -1363,6 +1388,15 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -1418,6 +1452,21 @@ ], "license": "CC-BY-4.0" }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1579,6 +1628,24 @@ "dev": true, "license": "ISC" }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -1859,6 +1926,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1884,6 +1957,12 @@ "dev": true, "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1903,6 +1982,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -1938,6 +2047,12 @@ "dev": true, "license": "MIT" }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.56.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", @@ -1992,6 +2107,27 @@ "node": ">=0.10.0" } }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", diff --git a/package.json b/package.json index 966679e..ce0b059 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "pinia": "^3.0.4", "vue": "^3.5.24", "vue-router": "^4.6.4" }, diff --git a/src/App.vue b/src/App.vue index e3427ad..13663d0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,60 @@ + + +import { RouterLink, RouterView, useRouter } from "vue-router"; +import { onMounted } from "vue"; +import { useAuthStore } from "@/stores/auth"; +import { useCartStore } from "@/stores/cart"; - \ No newline at end of file +const auth = useAuthStore(); +const cart = useCartStore(); +const router = useRouter(); + +onMounted(async () => { + if (auth.token) { + await auth.fetchMe(); + await cart.fetchCart(); + } +}); + +function logout() { + auth.logout(); + cart.items = []; + cart.total_price = 0; + router.push("/products"); +} + \ No newline at end of file diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue deleted file mode 100644 index 546ebbc..0000000 --- a/src/components/HelloWorld.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue deleted file mode 100644 index d64bbcb..0000000 --- a/src/layouts/AppLayout.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..e157687 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,63 @@ +// src/lib/api.ts +const API_BASE = import.meta.env.VITE_API_BASE ?? "/api"; +const TOKEN_KEY = "cloudshopt_token"; + +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); +} + +export function setToken(token: string | null) { + if (!token) localStorage.removeItem(TOKEN_KEY); + else localStorage.setItem(TOKEN_KEY, token); +} + +type ApiError = { + message?: string; + errors?: Record; +}; + +export async function api( + path: string, + opts: { method?: string; body?: any; auth?: boolean } = {} +): Promise { + const url = `${API_BASE}${path.startsWith("/") ? path : `/${path}`}`; + + const headers: Record = { + Accept: "application/json", + }; + + if (opts.body !== undefined) { + headers["Content-Type"] = "application/json"; + } + + if (opts.auth !== false) { + const token = getToken(); + if (token) headers["Authorization"] = `Bearer ${token}`; + } + + const res = await fetch(url, { + method: opts.method ?? "GET", + headers, + body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined, + }); + + const text = await res.text(); + const data = text ? (JSON.parse(text) as any) : null; + + if (!res.ok) { + const err: ApiError = data ?? { message: "Request failed" }; + const msg = err.message ?? `HTTP ${res.status}`; + throw Object.assign(new Error(msg), { status: res.status, details: err }); + } + + return data as T; +} + +// Helpers +export const apiGet = (p: string, auth = true) => api(p, { auth }); +export const apiPost = (p: string, body?: any, auth = true) => + api(p, { method: "POST", body, auth }); +export const apiPatch = (p: string, body?: any, auth = true) => + api(p, { method: "PATCH", body, auth }); +export const apiDel = (p: string, auth = true) => + api(p, { method: "DELETE", auth }); \ No newline at end of file diff --git a/src/main.js b/src/main.js index bb2365a..42c9471 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,7 @@ import { createApp } from "vue"; -import App from "./App.vue"; +import { createPinia } from "pinia"; import router from "./router"; - import "./style.css"; +import App from "./App.vue"; -createApp(App).use(router).mount("#app"); \ No newline at end of file +createApp(App).use(createPinia()).use(router).mount("#app"); \ No newline at end of file diff --git a/src/pages/NotFoundPage.vue b/src/pages/NotFoundPage.vue deleted file mode 100644 index 4113f87..0000000 --- a/src/pages/NotFoundPage.vue +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/OrdersPage.vue b/src/pages/OrdersPage.vue deleted file mode 100644 index ae3e2e0..0000000 --- a/src/pages/OrdersPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/src/pages/PaymentsPage.vue b/src/pages/PaymentsPage.vue deleted file mode 100644 index 60ccccd..0000000 --- a/src/pages/PaymentsPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/src/pages/ProductDetailPage.vue b/src/pages/ProductDetailPage.vue deleted file mode 100644 index 2c1536a..0000000 --- a/src/pages/ProductDetailPage.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - \ No newline at end of file diff --git a/src/pages/ProductsPage.vue b/src/pages/ProductsPage.vue deleted file mode 100644 index a787a3b..0000000 --- a/src/pages/ProductsPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/src/router.ts b/src/router.ts index 66bd694..3c6f65c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,21 +1,44 @@ +// src/router.ts import { createRouter, createWebHistory } from "vue-router"; +import { useAuthStore } from "@/stores/auth"; -import ProductsPage from "./pages/ProductsPage.vue"; -import ProductDetailPage from "./pages/ProductDetailPage.vue"; -import NotFoundPage from "./pages/NotFoundPage.vue"; -import OrdersPage from "./pages/OrdersPage.vue"; -import PaymentsPage from "./pages/PaymentsPage.vue"; +import ProductsView from "@/views/ProductsView.vue"; +import ProductDetailView from "@/views/ProductDetailView.vue"; +import LoginView from "@/views/LoginView.vue"; +import RegisterView from "@/views/RegisterView.vue"; +import CartView from "@/views/CartView.vue"; +import OrdersView from "@/views/OrdersView.vue"; +import OrderDetailView from "@/views/OrderDetailView.vue"; +import CheckoutSuccessView from "@/views/CheckoutSuccessView.vue"; +import CheckoutCancelView from "@/views/CheckoutCancelView.vue"; const router = createRouter({ history: createWebHistory(), routes: [ { path: "/", redirect: "/products" }, - { path: "/products", component: ProductsPage }, - { path: "/products/:id", component: ProductDetailPage, props: true }, - { path: "/orders", component: OrdersPage }, - { path: "/payments", component: PaymentsPage }, - { path: "/:pathMatch(.*)*", component: NotFoundPage } + + { path: "/login", component: LoginView }, + { path: "/register", component: RegisterView }, + + { path: "/products", component: ProductsView }, + { path: "/products/:id", component: ProductDetailView }, + + { path: "/cart", component: CartView, meta: { auth: true } }, + { path: "/orders", component: OrdersView, meta: { auth: true } }, + { path: "/orders/:id", component: OrderDetailView, meta: { auth: true } }, ], }); +router.beforeEach(async (to) => { + const auth = useAuthStore(); + + // ob refreshu: če je token, poskusi fetchMe enkrat + if (auth.token && !auth.user) await auth.fetchMe(); + + if (to.meta.auth && !auth.token) { + return { path: "/login", query: { next: to.fullPath } }; + } + return true; +}); + export default router; \ No newline at end of file diff --git a/src/stores/auth.ts b/src/stores/auth.ts new file mode 100644 index 0000000..b4f514c --- /dev/null +++ b/src/stores/auth.ts @@ -0,0 +1,73 @@ +// src/stores/auth.ts +import { defineStore } from "pinia"; +import { apiGet, apiPost, getToken, setToken } from "@/lib/api"; + +type User = { id: number; name: string; email: string }; + +export const useAuthStore = defineStore("auth", { + state: () => ({ + token: getToken() as string | null, + user: null as User | null, + loading: false, + }), + getters: { + isAuthed: (s) => !!s.token, + }, + actions: { + async fetchMe() { + if (!this.token) return; + try { + const r = await apiGet<{ user: User }>("/users/me", true); + this.user = r.user; + } catch { + // token invalid -> logout + this.logout(); + } + }, + + async register(payload: { + name: string; + email: string; + password: string; + password_confirmation: string; + }) { + this.loading = true; + try { + const r = await apiPost<{ + token: string; + user: User; + expires_in: number; + }>("/users/auth/register", payload, false); + + this.token = r.token; + setToken(r.token); + this.user = r.user; + } finally { + this.loading = false; + } + }, + + async login(payload: { email: string; password: string }) { + this.loading = true; + try { + const r = await apiPost<{ + token: string; + user: User; + expires_in: number; + }>("/users/auth/login", payload, false); + + this.token = r.token; + setToken(r.token); + this.user = r.user; + } finally { + this.loading = false; + } + }, + + logout() { + this.token = null; + this.user = null; + setToken(null); + }, + }, +}); \ No newline at end of file diff --git a/src/stores/cart.ts b/src/stores/cart.ts new file mode 100644 index 0000000..9de89db --- /dev/null +++ b/src/stores/cart.ts @@ -0,0 +1,63 @@ +// src/stores/cart.ts +import { defineStore } from "pinia"; +import { apiDel, apiGet, apiPatch, apiPost } from "@/lib/api"; + +export type CartItem = { + id: number; + product_id: number; + name_snapshot: string; + unit_price_snapshot: number; + qty: number; +}; + +export type CartResponse = { + data: { + id: number; + items: CartItem[]; + total_price: number; + }; +}; + +export const useCartStore = defineStore("cart", { + state: () => ({ + items: [] as CartItem[], + total_price: 0, + loading: false, + }), + getters: { + count: (s) => s.items.reduce((a, i) => a + i.qty, 0), + }, + actions: { + async fetchCart() { + this.loading = true; + try { + const r = await apiGet("/orders/cart", true); + this.items = r.data.items ?? []; + this.total_price = r.data.total_price ?? 0; + } finally { + this.loading = false; + } + }, + + async addItem(product_id: number, qty = 1) { + await apiPost("/orders/cart/items", { product_id, qty }, true); + await this.fetchCart(); + }, + + async updateItem(itemId: number, qty: number) { + await apiPatch(`/orders/cart/items/${itemId}`, { qty }, true); + await this.fetchCart(); + }, + + async removeItem(itemId: number) { + await apiDel(`/orders/cart/items/${itemId}`, true); + await this.fetchCart(); + }, + + async createOrderFromCart() { + const r = await apiPost<{ data: any }>("/orders/items", undefined, true); + await this.fetchCart(); + return r.data; + }, + }, +}); \ No newline at end of file diff --git a/src/style.css b/src/style.css index beb8313..ef63c5e 100644 --- a/src/style.css +++ b/src/style.css @@ -1,81 +1,81 @@ @import "tailwindcss"; -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; +/*:root {*/ +/* font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;*/ +/* line-height: 1.5;*/ +/* font-weight: 400;*/ - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; +/* color-scheme: light dark;*/ +/* color: rgba(255, 255, 255, 0.87);*/ +/* background-color: #242424;*/ - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} +/* font-synthesis: none;*/ +/* text-rendering: optimizeLegibility;*/ +/* -webkit-font-smoothing: antialiased;*/ +/* -moz-osx-font-smoothing: grayscale;*/ +/*}*/ -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} +/*a {*/ +/* font-weight: 500;*/ +/* color: #646cff;*/ +/* text-decoration: inherit;*/ +/*}*/ +/*a:hover {*/ +/* color: #535bf2;*/ +/*}*/ -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} +/*body {*/ +/* margin: 0;*/ +/* display: flex;*/ +/* place-items: center;*/ +/* min-width: 320px;*/ +/* min-height: 100vh;*/ +/*}*/ -h1 { - font-size: 3.2em; - line-height: 1.1; -} +/*h1 {*/ +/* font-size: 3.2em;*/ +/* line-height: 1.1;*/ +/*}*/ -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} +/*button {*/ +/* border-radius: 8px;*/ +/* border: 1px solid transparent;*/ +/* padding: 0.6em 1.2em;*/ +/* font-size: 1em;*/ +/* font-weight: 500;*/ +/* font-family: inherit;*/ +/* background-color: #1a1a1a;*/ +/* cursor: pointer;*/ +/* transition: border-color 0.25s;*/ +/*}*/ +/*button:hover {*/ +/* border-color: #646cff;*/ +/*}*/ +/*button:focus,*/ +/*button:focus-visible {*/ +/* outline: 4px auto -webkit-focus-ring-color;*/ +/*}*/ -.card { - padding: 2em; -} +/*.card {*/ +/* padding: 2em;*/ +/*}*/ -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} +/*#app {*/ +/* max-width: 1280px;*/ +/* margin: 0 auto;*/ +/* padding: 2rem;*/ +/* text-align: center;*/ +/*}*/ -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +/*@media (prefers-color-scheme: light) {*/ +/* :root {*/ +/* color: #213547;*/ +/* background-color: #ffffff;*/ +/* }*/ +/* a:hover {*/ +/* color: #747bff;*/ +/* }*/ +/* button {*/ +/* background-color: #f9f9f9;*/ +/* }*/ +/*}*/ diff --git a/src/views/CartView.vue b/src/views/CartView.vue new file mode 100644 index 0000000..14848e4 --- /dev/null +++ b/src/views/CartView.vue @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue new file mode 100644 index 0000000..a59171a --- /dev/null +++ b/src/views/LoginView.vue @@ -0,0 +1,48 @@ + + + \ No newline at end of file diff --git a/src/views/OrderDetailView.vue b/src/views/OrderDetailView.vue new file mode 100644 index 0000000..ed25e89 --- /dev/null +++ b/src/views/OrderDetailView.vue @@ -0,0 +1,54 @@ + + + \ No newline at end of file diff --git a/src/views/OrdersView.vue b/src/views/OrdersView.vue new file mode 100644 index 0000000..9465fc3 --- /dev/null +++ b/src/views/OrdersView.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/src/views/ProductDetailView.vue b/src/views/ProductDetailView.vue new file mode 100644 index 0000000..e3fe961 --- /dev/null +++ b/src/views/ProductDetailView.vue @@ -0,0 +1,67 @@ + + + \ No newline at end of file diff --git a/src/views/ProductsView.vue b/src/views/ProductsView.vue new file mode 100644 index 0000000..b030976 --- /dev/null +++ b/src/views/ProductsView.vue @@ -0,0 +1,74 @@ + + + \ No newline at end of file diff --git a/src/views/RegisterView.vue b/src/views/RegisterView.vue new file mode 100644 index 0000000..1521a96 --- /dev/null +++ b/src/views/RegisterView.vue @@ -0,0 +1,59 @@ + + + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 9c4a622..5627380 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,13 @@ 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(), tailwindcss()], -}) \ No newline at end of file + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, +})