create-nexwire 4.2.3 → 4.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/express/.vscode/extensions.json +6 -0
- package/express/.vscode/settings.json +37 -26
- package/express/biome.json +99 -66
- package/express/package.json +54 -27
- package/express/src/database/migrations/sqlite/0000_init.sql +47 -60
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -89
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/database/schema.ts +0 -1
- package/express/src/framework/facade.ts +12 -1
- package/express/src/framework/maker-cli/db/core.mjs +291 -288
- package/express/src/framework/maker-cli/module/core.mjs +125 -87
- package/express/src/framework/maker-cli/module/index.mjs +3 -3
- package/express/src/framework/maker-cli/stubs/notification/controller.ts.stub +61 -62
- package/express/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -13
- package/express/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/express/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +11 -10
- package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +1 -1
- package/express/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +25 -25
- package/express/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -25
- package/express/src/framework/notification/index.ts +49 -87
- package/express/src/framework/providers/adapter.ts +24 -10
- package/express/src/framework/providers/contracts.ts +80 -10
- package/express/src/framework/providers/registry.ts +27 -1
- package/express/src/resources/src/components/Checkbox.vue +32 -29
- package/express/src/resources/src/components/Input.vue +243 -223
- package/express/src/resources/src/components/InputGroup.vue +147 -145
- package/express/src/resources/src/components/InputPasswordToggle.vue +229 -213
- package/express/src/resources/src/components/Modal.vue +71 -55
- package/express/src/resources/src/components/Refresh.vue +19 -0
- package/express/src/resources/src/helpers/assets.ts +51 -0
- package/express/src/resources/src/main.ts +4 -2
- package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -1
- package/express/src/resources/src/pages/auth/login.vue +1 -1
- package/express/src/resources/src/pages/auth/register.vue +1 -1
- package/express/src/resources/src/pages/auth/resetPassword.vue +1 -1
- package/express/src/resources/src/pages/auth/verifyEmail.vue +1 -1
- package/express/src/resources/src/pages/dashboard/index.vue +66 -66
- package/express/src/resources/src/plugins/gum.ts +1 -1
- package/express/src/resources/src/stores/auth.ts +184 -164
- package/express/src/resources/vite.config.ts +9 -2
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/hono/.vscode/extensions.json +6 -0
- package/hono/.vscode/settings.json +37 -26
- package/hono/biome.json +25 -2
- package/hono/package.json +50 -23
- package/hono/src/database/migrations/sqlite/0000_init.sql +47 -60
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -89
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/database/schema.ts +0 -1
- package/hono/src/framework/facade.ts +12 -1
- package/hono/src/framework/maker-cli/db/core.mjs +291 -288
- package/hono/src/framework/maker-cli/module/core.mjs +93 -52
- package/hono/src/framework/maker-cli/module/index.mjs +3 -3
- package/hono/src/framework/maker-cli/stubs/notification/controller.ts.stub +63 -58
- package/hono/src/framework/maker-cli/stubs/notification/job.ts.stub +10 -12
- package/hono/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/hono/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +11 -10
- package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +1 -1
- package/hono/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +25 -25
- package/hono/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -25
- package/hono/src/framework/notification/index.ts +49 -87
- package/hono/src/framework/providers/adapter.ts +24 -10
- package/hono/src/framework/providers/contracts.ts +80 -10
- package/hono/src/framework/providers/registry.ts +27 -1
- package/hono/src/resources/src/components/Checkbox.vue +32 -29
- package/hono/src/resources/src/components/Input.vue +243 -234
- package/hono/src/resources/src/components/InputGroup.vue +147 -149
- package/hono/src/resources/src/components/InputPasswordToggle.vue +229 -222
- package/hono/src/resources/src/components/Modal.vue +71 -55
- package/hono/src/resources/src/components/Refresh.vue +19 -0
- package/hono/src/resources/src/helpers/assets.ts +51 -0
- package/hono/src/resources/src/main.ts +4 -2
- package/hono/src/resources/src/pages/auth/forgetPassword.vue +1 -1
- package/hono/src/resources/src/pages/auth/login.vue +1 -1
- package/hono/src/resources/src/pages/auth/register.vue +1 -1
- package/hono/src/resources/src/pages/auth/resetPassword.vue +1 -1
- package/hono/src/resources/src/pages/auth/verifyEmail.vue +1 -1
- package/hono/src/resources/src/pages/dashboard/index.vue +66 -66
- package/hono/src/resources/src/plugins/gum.ts +1 -1
- package/hono/src/resources/src/stores/auth.ts +184 -164
- package/hono/src/resources/vite.config.ts +9 -2
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +10 -10
- package/express/src/modules/auth/database/models/notification.ts +0 -24
- package/hono/src/modules/auth/database/models/notification.ts +0 -24
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/helpers/assets.ts
|
|
2
|
+
|
|
3
|
+
// 1. Eagerly load ALL images in the entire src folder.
|
|
4
|
+
// This tells Vite: "Find every image, hash it, and give me the final URL."
|
|
5
|
+
const allImages = import.meta.glob("/src/**/*.{png,jpg,jpeg,svg,webp,gif}", {
|
|
6
|
+
eager: true,
|
|
7
|
+
import: "default"
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves ANY image path (using @, relative, or absolute) for both dev and prod.
|
|
12
|
+
*/
|
|
13
|
+
export default function asset(path: string): string {
|
|
14
|
+
// 1. If it's already a full URL or data URI, return it as is.
|
|
15
|
+
if (path.startsWith("http") || path.startsWith("data:")) {
|
|
16
|
+
return path;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Normalize the path.
|
|
20
|
+
// Vite's glob keys always start from the root, like "/src/assets/..."
|
|
21
|
+
let cleanPath = path;
|
|
22
|
+
|
|
23
|
+
// Convert alias `@/` to `/src/`
|
|
24
|
+
if (cleanPath.startsWith("@/")) {
|
|
25
|
+
cleanPath = cleanPath.replace("@/", "/src/");
|
|
26
|
+
}
|
|
27
|
+
// Convert absolute `/` to `/src/` (assuming you mean inside src)
|
|
28
|
+
else if (cleanPath.startsWith("/") && !cleanPath.startsWith("/src/")) {
|
|
29
|
+
cleanPath = `/src${cleanPath}`;
|
|
30
|
+
}
|
|
31
|
+
// Convert relative `../../` (This is the tricky part)
|
|
32
|
+
else if (cleanPath.startsWith("../")) {
|
|
33
|
+
// We can't easily resolve `../../` from a utility file, so we just
|
|
34
|
+
// try to find a matching suffix in the glob keys.
|
|
35
|
+
// (e.g., if you pass "../../assets/foo.png", we look for "assets/foo.png" inside /src)
|
|
36
|
+
const match = Object.keys(allImages).find((key) => key.endsWith(cleanPath.replace(/^(\.\.\/)+/, "")));
|
|
37
|
+
if (match) {
|
|
38
|
+
return allImages[match] as string;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. Look up the normalized path in the glob object
|
|
43
|
+
const resolved = allImages[cleanPath] as string | undefined;
|
|
44
|
+
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
console.warn(`[getAssetUrl] Image not found: ${path}. Returning original path.`);
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "@/plugins/axios";
|
|
2
|
-
import { createHead } from "@
|
|
2
|
+
import { createHead } from "@unhead/vue/client";
|
|
3
3
|
import { createPinia } from "pinia";
|
|
4
4
|
import { createApp } from "vue";
|
|
5
5
|
import { DialogPlugin } from "@/plugins/dialog";
|
|
@@ -15,7 +15,9 @@ import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
|
|
15
15
|
|
|
16
16
|
const app = createApp(App);
|
|
17
17
|
const pinia = createPinia();
|
|
18
|
-
const head = createHead({
|
|
18
|
+
const head = createHead({
|
|
19
|
+
init: [{ titleTemplate: (title) => (title ? `${title} | nexwire` : "nexwire") }],
|
|
20
|
+
});
|
|
19
21
|
|
|
20
22
|
app.use(pinia);
|
|
21
23
|
app.use(router);
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
</template>
|
|
54
54
|
|
|
55
55
|
<script setup lang="ts">
|
|
56
|
-
import { useHead } from "@
|
|
56
|
+
import { useHead } from "@unhead/vue";
|
|
57
57
|
import { ref } from "vue";
|
|
58
58
|
import { useRoute, useRouter } from "vue-router";
|
|
59
59
|
import Button from "@/components/Button.vue";
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Pagebar title="Dashboard" />
|
|
3
|
-
|
|
4
|
-
<div class="dashboard-placeholder">
|
|
5
|
-
<div class="row g-2 mb-2">
|
|
6
|
-
<div class="col-12 col-md-4">
|
|
7
|
-
<div class="placeholder-card"></div>
|
|
8
|
-
</div>
|
|
9
|
-
<div class="col-12 col-md-4">
|
|
10
|
-
<div class="placeholder-card"></div>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="col-12 col-md-4">
|
|
13
|
-
<div class="placeholder-card"></div>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<div class="placeholder-panel"></div>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
20
|
-
|
|
21
|
-
<script setup lang="ts">
|
|
22
|
-
import { useHead } from "@
|
|
23
|
-
import { onMounted, onUnmounted } from "vue";
|
|
24
|
-
import Pagebar from "@/components/Pagebar.vue";
|
|
25
|
-
import { useAuth } from "@/composables/useAuth";
|
|
26
|
-
import { pulse } from "@/plugins/pulse";
|
|
27
|
-
|
|
28
|
-
useHead({ title: "Dashboard" });
|
|
29
|
-
|
|
30
|
-
const { user } = useAuth();
|
|
31
|
-
|
|
32
|
-
onMounted(() => {
|
|
33
|
-
const ch = pulse.channel("user");
|
|
34
|
-
ch.listen("user.changed", () => alert("me"));
|
|
35
|
-
onUnmounted(() => ch.stopListening("user.changed"));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// const hardRefresh = () => window.location.reload();
|
|
39
|
-
</script>
|
|
40
|
-
|
|
41
|
-
<style scoped>
|
|
42
|
-
.placeholder-card,
|
|
43
|
-
.placeholder-panel {
|
|
44
|
-
border: 1px solid var(--app-border);
|
|
45
|
-
border-radius: 0.3rem;
|
|
46
|
-
background: var(--app-surface);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.placeholder-card {
|
|
50
|
-
min-height: 220px;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.placeholder-panel {
|
|
54
|
-
min-height: 520px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@media (max-width: 768px) {
|
|
58
|
-
.placeholder-card {
|
|
59
|
-
min-height: 160px;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.placeholder-panel {
|
|
63
|
-
min-height: 360px;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<Pagebar title="Dashboard" />
|
|
3
|
+
|
|
4
|
+
<div class="dashboard-placeholder">
|
|
5
|
+
<div class="row g-2 mb-2">
|
|
6
|
+
<div class="col-12 col-md-4">
|
|
7
|
+
<div class="placeholder-card"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="col-12 col-md-4">
|
|
10
|
+
<div class="placeholder-card"></div>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="col-12 col-md-4">
|
|
13
|
+
<div class="placeholder-card"></div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="placeholder-panel"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { useHead } from "@unhead/vue";
|
|
23
|
+
import { onMounted, onUnmounted } from "vue";
|
|
24
|
+
import Pagebar from "@/components/Pagebar.vue";
|
|
25
|
+
import { useAuth } from "@/composables/useAuth";
|
|
26
|
+
import { pulse } from "@/plugins/pulse";
|
|
27
|
+
|
|
28
|
+
useHead({ title: "Dashboard" });
|
|
29
|
+
|
|
30
|
+
const { user } = useAuth();
|
|
31
|
+
|
|
32
|
+
onMounted(() => {
|
|
33
|
+
const ch = pulse.channel("user");
|
|
34
|
+
ch.listen("user.changed", () => alert("me"));
|
|
35
|
+
onUnmounted(() => ch.stopListening("user.changed"));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// const hardRefresh = () => window.location.reload();
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
.placeholder-card,
|
|
43
|
+
.placeholder-panel {
|
|
44
|
+
border: 1px solid var(--app-border);
|
|
45
|
+
border-radius: 0.3rem;
|
|
46
|
+
background: var(--app-surface);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.placeholder-card {
|
|
50
|
+
min-height: 220px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.placeholder-panel {
|
|
54
|
+
min-height: 520px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (max-width: 768px) {
|
|
58
|
+
.placeholder-card {
|
|
59
|
+
min-height: 160px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.placeholder-panel {
|
|
63
|
+
min-height: 360px;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
@@ -50,7 +50,7 @@ type ValidationErrorPayload = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
function normalizeValidationErrors(payload?: ValidationErrorPayload): NormalizedErrors {
|
|
53
|
-
const normalized: NormalizedErrors = { ...
|
|
53
|
+
const normalized: NormalizedErrors = { ...payload?.errors };
|
|
54
54
|
|
|
55
55
|
const issues = payload?.error?.issues;
|
|
56
56
|
if (Array.isArray(issues)) {
|
|
@@ -1,164 +1,184 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { defineStore } from "pinia";
|
|
3
|
-
import { ref } from "vue";
|
|
4
|
-
import { type AuthUser, clearUser, setUser } from "@/composables/useAuth";
|
|
5
|
-
|
|
6
|
-
type LoginPayload = { email
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return data.message || "
|
|
125
|
-
} finally {
|
|
126
|
-
processing.value = false;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
processing.value = true;
|
|
132
|
-
try {
|
|
133
|
-
const data = await request<unknown>("POST", "/
|
|
134
|
-
return data.message || "
|
|
135
|
-
} finally {
|
|
136
|
-
processing.value = false;
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
processing.value = true;
|
|
142
|
-
try {
|
|
143
|
-
await request<unknown>("POST", "/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
processing.value = false;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { defineStore } from "pinia";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import { type AuthUser, clearUser, setUser } from "@/composables/useAuth";
|
|
5
|
+
|
|
6
|
+
type LoginPayload = { email?: string; phone?: string; password: string; remember?: boolean };
|
|
7
|
+
type VerifyEmailPayload = { email: string; token: string };
|
|
8
|
+
type ForgotPayload = { email: string };
|
|
9
|
+
|
|
10
|
+
type RegisterPayload = {
|
|
11
|
+
name: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
phone?: string;
|
|
14
|
+
password: string;
|
|
15
|
+
password_confirmation: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ResetPayload = {
|
|
19
|
+
email: string;
|
|
20
|
+
token: string;
|
|
21
|
+
password: string;
|
|
22
|
+
password_confirmation: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ApiResponse<T> = { message: string; data?: T };
|
|
26
|
+
type AuthData = { user: AuthUser };
|
|
27
|
+
|
|
28
|
+
async function request<T>(method: "GET" | "POST", path: string, payload?: unknown): Promise<ApiResponse<T>> {
|
|
29
|
+
try {
|
|
30
|
+
const response = await axios.request<ApiResponse<T>>({
|
|
31
|
+
method,
|
|
32
|
+
url: `/api/auth${path}`,
|
|
33
|
+
data: payload
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return response.data;
|
|
37
|
+
} catch (error: unknown) {
|
|
38
|
+
if (axios.isAxiosError(error)) {
|
|
39
|
+
throw new Error(String(error.response?.data?.message || error.message || "Request failed"));
|
|
40
|
+
}
|
|
41
|
+
throw new Error("Request failed");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const MAX_BOOTSTRAP_ATTEMPTS = 5;
|
|
46
|
+
const BOOTSTRAP_BACKOFF_MS = 500;
|
|
47
|
+
|
|
48
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
|
|
50
|
+
export const useAuthStore = defineStore("auth", () => {
|
|
51
|
+
const user = ref<AuthUser | null>(null);
|
|
52
|
+
const isAuthenticated = ref(false);
|
|
53
|
+
const processing = ref(false);
|
|
54
|
+
const initialized = ref(false);
|
|
55
|
+
|
|
56
|
+
const syncUser = (value: AuthUser | null) => {
|
|
57
|
+
user.value = value;
|
|
58
|
+
isAuthenticated.value = !!value;
|
|
59
|
+
if (value) setUser(value);
|
|
60
|
+
else clearUser();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let bootstrapPromise: Promise<void> | null = null;
|
|
64
|
+
const bootstrap = async () => {
|
|
65
|
+
if (initialized.value) return;
|
|
66
|
+
|
|
67
|
+
// Reuse the same request if multiple navigations call bootstrap.
|
|
68
|
+
if (bootstrapPromise) {
|
|
69
|
+
return bootstrapPromise;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
bootstrapPromise = (async () => {
|
|
73
|
+
for (let attempt = 1; attempt <= MAX_BOOTSTRAP_ATTEMPTS; attempt++) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await axios.get<ApiResponse<AuthUser>>("/api/auth/me");
|
|
76
|
+
|
|
77
|
+
syncUser((response.data?.data || null) as AuthUser | null);
|
|
78
|
+
initialized.value = true;
|
|
79
|
+
return;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const backendUnreachable = axios.isAxiosError(error) && !error.response;
|
|
82
|
+
|
|
83
|
+
if (!backendUnreachable || attempt === MAX_BOOTSTRAP_ATTEMPTS) {
|
|
84
|
+
syncUser(null);
|
|
85
|
+
initialized.value = true;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await sleep(BOOTSTRAP_BACKOFF_MS * 2 ** (attempt - 1));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await bootstrapPromise;
|
|
96
|
+
} finally {
|
|
97
|
+
bootstrapPromise = null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const login = async (payload: LoginPayload) => {
|
|
102
|
+
processing.value = true;
|
|
103
|
+
try {
|
|
104
|
+
const data = await request<AuthData>("POST", "/login", payload);
|
|
105
|
+
syncUser((data?.data?.user || null) as AuthUser | null);
|
|
106
|
+
initialized.value = true;
|
|
107
|
+
return data.message || "Login successful";
|
|
108
|
+
} finally {
|
|
109
|
+
processing.value = false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const register = async (payload: RegisterPayload) => {
|
|
114
|
+
processing.value = true;
|
|
115
|
+
try {
|
|
116
|
+
const data = await request<AuthData>("POST", "/register", payload);
|
|
117
|
+
const createdUser = (data?.data?.user || null) as AuthUser | null;
|
|
118
|
+
if (createdUser) {
|
|
119
|
+
syncUser(createdUser);
|
|
120
|
+
initialized.value = true;
|
|
121
|
+
} else {
|
|
122
|
+
syncUser(null);
|
|
123
|
+
}
|
|
124
|
+
return data.message || "Registration successful";
|
|
125
|
+
} finally {
|
|
126
|
+
processing.value = false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const verifyEmail = async (payload: VerifyEmailPayload) => {
|
|
131
|
+
processing.value = true;
|
|
132
|
+
try {
|
|
133
|
+
const data = await request<unknown>("POST", "/verify-email", payload);
|
|
134
|
+
return data.message || "Email verified successfully";
|
|
135
|
+
} finally {
|
|
136
|
+
processing.value = false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const forgotPassword = async (payload: ForgotPayload) => {
|
|
141
|
+
processing.value = true;
|
|
142
|
+
try {
|
|
143
|
+
const data = await request<unknown>("POST", "/forgot-password", payload);
|
|
144
|
+
return data.message || "If this email exists, a reset link has been sent";
|
|
145
|
+
} finally {
|
|
146
|
+
processing.value = false;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const resetPassword = async (payload: ResetPayload) => {
|
|
151
|
+
processing.value = true;
|
|
152
|
+
try {
|
|
153
|
+
const data = await request<unknown>("POST", "/reset-password", payload);
|
|
154
|
+
return data.message || "Password reset successfully";
|
|
155
|
+
} finally {
|
|
156
|
+
processing.value = false;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const logout = async () => {
|
|
161
|
+
processing.value = true;
|
|
162
|
+
try {
|
|
163
|
+
await request<unknown>("POST", "/logout");
|
|
164
|
+
} finally {
|
|
165
|
+
syncUser(null);
|
|
166
|
+
processing.value = false;
|
|
167
|
+
initialized.value = true;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
user,
|
|
173
|
+
isAuthenticated,
|
|
174
|
+
processing,
|
|
175
|
+
initialized,
|
|
176
|
+
bootstrap,
|
|
177
|
+
register,
|
|
178
|
+
login,
|
|
179
|
+
forgotPassword,
|
|
180
|
+
resetPassword,
|
|
181
|
+
verifyEmail,
|
|
182
|
+
logout
|
|
183
|
+
};
|
|
184
|
+
});
|
|
@@ -20,13 +20,20 @@ export default defineConfig({
|
|
|
20
20
|
__SOCKET_ENABLED__: socketEnabled,
|
|
21
21
|
__API_URL__: JSON.stringify(apiUrl)
|
|
22
22
|
},
|
|
23
|
-
root: "
|
|
23
|
+
root: fileURLToPath(new URL(".", import.meta.url)),
|
|
24
24
|
cacheDir: path.join(cacheBase, "nexwire", "vite-cache", "resources"),
|
|
25
25
|
plugins: [vue()],
|
|
26
26
|
css: {
|
|
27
27
|
preprocessorOptions: {
|
|
28
28
|
scss: {
|
|
29
|
-
quietDeps: true
|
|
29
|
+
quietDeps: true,
|
|
30
|
+
silenceDeprecations: [
|
|
31
|
+
"import",
|
|
32
|
+
"if-function",
|
|
33
|
+
"global-builtin",
|
|
34
|
+
"color-functions",
|
|
35
|
+
"slash-div",
|
|
36
|
+
],
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
39
|
},
|
|
Binary file
|