Skip to content

Commit 292699f

Browse files
AchoArnoldCopilot
andcommitted
feat(web): create all Pinia stores and route middleware
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3dfcecc commit 292699f

9 files changed

Lines changed: 832 additions & 0 deletions

File tree

web/app/middleware/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default defineNuxtRouteMiddleware((to) => {
2+
const authStore = useAuthStore();
3+
if (authStore.authUser === null) {
4+
return navigateTo({ path: "/login", query: { to: to.path } });
5+
}
6+
});

web/app/middleware/guest.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default defineNuxtRouteMiddleware(() => {
2+
const authStore = useAuthStore();
3+
if (authStore.authUser !== null) {
4+
return navigateTo("/threads");
5+
}
6+
});

web/app/stores/app.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { defineStore } from "pinia";
2+
3+
export interface AppData {
4+
url: string;
5+
name: string;
6+
env: string;
7+
appDownloadUrl: string;
8+
documentationUrl: string;
9+
githubUrl: string;
10+
}
11+
12+
export const useAppStore = defineStore("app", () => {
13+
const config = useRuntimeConfig();
14+
const polling = ref(false);
15+
16+
const appData = computed<AppData>(() => {
17+
let url = (config.public.appUrl as string) || "";
18+
if (url.length > 0 && url[url.length - 1] === "/") {
19+
url = url.substring(0, url.length - 1);
20+
}
21+
return {
22+
url,
23+
env: config.public.appEnv as string,
24+
appDownloadUrl: config.public.appDownloadUrl as string,
25+
documentationUrl: config.public.appDocumentationUrl as string,
26+
githubUrl: config.public.appGithubUrl as string,
27+
name: config.public.appName as string,
28+
};
29+
});
30+
31+
const isLocal = computed(() => config.public.appEnv === "local");
32+
33+
function setPolling(value: boolean) {
34+
polling.value = value;
35+
}
36+
37+
return {
38+
polling,
39+
appData,
40+
isLocal,
41+
setPolling,
42+
};
43+
});

web/app/stores/auth.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { defineStore } from "pinia";
2+
import { setAuthHeader, setApiKey } from "~/composables/useApi";
3+
import type { User } from "~~/shared/types/user";
4+
5+
export interface AuthUser {
6+
email: string | null;
7+
displayName: string | null;
8+
id: string;
9+
}
10+
11+
export const useAuthStore = defineStore("auth", () => {
12+
const authStateChanged = ref(false);
13+
const authUser = ref<AuthUser | null>(null);
14+
const user = ref<User | null>(null);
15+
const { apiFetch } = useApi();
16+
17+
async function setAuthUserAction(newUser: AuthUser | null | undefined) {
18+
const userChanged = newUser?.id !== authUser.value?.id;
19+
authUser.value = newUser ?? null;
20+
authStateChanged.value = true;
21+
22+
if (userChanged && newUser !== null) {
23+
await Promise.all([loadUser(), loadPhones()]);
24+
}
25+
}
26+
27+
async function onAuthStateChanged(firebaseUser: any) {
28+
if (firebaseUser == null) {
29+
authUser.value = null;
30+
user.value = null;
31+
authStateChanged.value = true;
32+
setApiKey("");
33+
return;
34+
}
35+
setAuthHeader(await firebaseUser.getIdToken());
36+
const { uid, email, displayName } = firebaseUser;
37+
authUser.value = { id: uid, email, displayName };
38+
authStateChanged.value = true;
39+
}
40+
41+
async function onIdTokenChanged(firebaseUser: any) {
42+
if (firebaseUser == null) {
43+
setApiKey("");
44+
return;
45+
}
46+
setAuthHeader(await firebaseUser.getIdToken());
47+
}
48+
49+
async function loadUser() {
50+
const response = await apiFetch<{ data: User }>("/v1/users/me");
51+
user.value = response.data;
52+
}
53+
54+
async function updateUser(payload: { owner?: string; timezone?: string }) {
55+
const phonesStore = usePhonesStore();
56+
if (payload.owner) {
57+
phonesStore.setOwner(payload.owner);
58+
}
59+
60+
const activePhone = phonesStore.activePhone;
61+
if (!activePhone) return;
62+
63+
const response = await apiFetch<{ data: User }>("/v1/users/me", {
64+
method: "PUT",
65+
body: {
66+
active_phone_id: activePhone.id,
67+
timezone: payload.timezone ?? user.value?.timezone,
68+
},
69+
});
70+
71+
setApiKey(response.data.api_key);
72+
user.value = response.data;
73+
}
74+
75+
async function deleteUserAccount(): Promise<string> {
76+
const response = await apiFetch<{ message: string }>("/v1/users/me", {
77+
method: "DELETE",
78+
});
79+
return response.message;
80+
}
81+
82+
async function rotateApiKey(userId: string): Promise<User> {
83+
const response = await apiFetch<{ data: User }>(
84+
`/v1/users/${userId}/api-keys`,
85+
{
86+
method: "DELETE",
87+
},
88+
);
89+
user.value = response.data;
90+
setApiKey(response.data.api_key);
91+
return response.data;
92+
}
93+
94+
function resetState() {
95+
user.value = null;
96+
authUser.value = null;
97+
authStateChanged.value = true;
98+
setApiKey("");
99+
}
100+
101+
function loadPhones() {
102+
const phonesStore = usePhonesStore();
103+
return phonesStore.loadPhones(false);
104+
}
105+
106+
return {
107+
authStateChanged,
108+
authUser,
109+
user,
110+
setAuthUserAction,
111+
onAuthStateChanged,
112+
onIdTokenChanged,
113+
loadUser,
114+
updateUser,
115+
deleteUserAccount,
116+
rotateApiKey,
117+
resetState,
118+
};
119+
});

0 commit comments

Comments
 (0)