Skip to content

Commit addbeb0

Browse files
committed
feature: dashboard
1 parent 7ce1496 commit addbeb0

File tree

12 files changed

+223
-17
lines changed

12 files changed

+223
-17
lines changed

api/authApiList.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { authFetch } from "./authFetch";
2+
3+
export const authApiList = {
4+
getAllForms() {
5+
return authFetch({
6+
url: "/main/main/forms",
7+
method: "GET",
8+
});
9+
},
10+
};

api/authFetch.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
let __globalTokenRefresher: undefined | Promise<string> = undefined;
2+
3+
export async function authFetch(
4+
P: {
5+
url: string;
6+
} & (
7+
| { method: "POST"; body?: any }
8+
| { method: "GET"; body?: { [k: string]: string } }
9+
),
10+
accessTokenNew: string | undefined = undefined
11+
) {
12+
const {
13+
public: { serverAddress },
14+
} = useRuntimeConfig();
15+
16+
const { url, method, body } = P;
17+
18+
const accessToken = accessTokenNew
19+
? accessTokenNew
20+
: localStorage.getItem(ACCESS_TOKEN_KEY);
21+
22+
const endUrl = `${serverAddress}${url}`;
23+
24+
const res = await fetch(
25+
method == "GET" && body != undefined
26+
? `${endUrl}?${Object.entries(body)
27+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
28+
.join("&")}`
29+
: endUrl,
30+
{
31+
method,
32+
body: method == "POST" ? body : undefined,
33+
headers: {
34+
...(method == "POST" ? { "Content-Type": "application/json" } : {}),
35+
Authorization: `Bearer ${accessToken}`,
36+
},
37+
}
38+
);
39+
40+
if (res.status == 403) {
41+
if (__globalTokenRefresher == undefined) {
42+
__globalTokenRefresher = refreshToken(serverAddress);
43+
}
44+
45+
const newAccessToken = await __globalTokenRefresher;
46+
localStorage.setItem(ACCESS_TOKEN_KEY, newAccessToken);
47+
__globalTokenRefresher = undefined;
48+
49+
return await authFetch(P, newAccessToken);
50+
} else return res;
51+
}
52+
53+
async function refreshToken(serverAddress: string) {
54+
console.info(` --- fetching new access token ...`);
55+
56+
const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);
57+
58+
const url = `${serverAddress}/main/main/refresh?token=${refreshToken}`;
59+
60+
const res = await fetch(url, { method: "POST" });
61+
62+
if (res.ok) {
63+
console.info(` --- access token fetched!`);
64+
return (await res.json()).data.access;
65+
} else if (res.status == 403) {
66+
throw new NotLoggedInError();
67+
} else {
68+
throw new Error(
69+
`refresh-token-err, status=${res.status}, text=${await res.text()}`
70+
);
71+
}
72+
}
73+
74+
export class NotLoggedInError extends Error {
75+
constructor() {
76+
super();
77+
78+
this.name = "NotLoggedInError";
79+
}
80+
}

app.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
<template>
2-
<div dir="rtl" class="bg-[#f9eff0] min-h-screen app-font">
2+
<div dir="rtl" class="bg-[#f9eff0] min-h-screen h-fit overflow-auto app-font">
33
<NuxtRouteAnnouncer />
4-
<NuxtLayout>
5-
<NuxtPage />
6-
</NuxtLayout>
7-
4+
<NuxtPage />
85
<Alert />
96
</div>
107
</template>

components/ActionButton.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<button class="bg-[#3e3e3e] text-white rounded-xl">
3+
<slot></slot>
4+
</button>
5+
</template>

layouts/DefaultLayout.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<template>
2+
<div class="py-10 max-w-4xl m-auto h-[90vh]">
3+
<slot name="header"></slot>
4+
<div class="rounded-xl p-3 w-full h-full">
5+
<slot></slot>
6+
</div>
7+
</div>
8+
</template>
9+
10+
<script setup lang="ts">
11+
defineSlots<{ header: any; default: any }>();
12+
</script>

layouts/default.vue

Lines changed: 0 additions & 5 deletions
This file was deleted.

nuxt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ export default defineNuxtConfig({
1111
tailwindcss: { config: {}, editorSupport: { autocompleteUtil: true } },
1212

1313
modules: ["@nuxtjs/tailwindcss", "@nuxt/fonts", "@nuxt/icon", "@pinia/nuxt"],
14-
});
14+
});

pages/dashboard.vue

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,69 @@
1-
<template>Hello logged in boy</template>
1+
<script setup lang="ts">
2+
import { authApiList } from "~/api/authApiList";
3+
import { NotLoggedInError } from "~/api/authFetch";
4+
import DefaultLayout from "~/layouts/DefaultLayout.vue";
5+
import type { Forms, FormsRes } from "~/types/Forms";
6+
7+
const formList = ref<Forms>([]);
8+
const loading = ref(true);
9+
const { showAlert } = useAlertStore();
10+
11+
async function fetchList() {
12+
loading.value = true;
13+
14+
try {
15+
const res = await authApiList.getAllForms();
16+
17+
if (res.ok) {
18+
const body: FormsRes = await res.json();
19+
20+
formList.value = body.data.data;
21+
}
22+
} catch (error) {
23+
if (error instanceof NotLoggedInError) {
24+
showAlert("باید دوباره وارد شوید.", "warn");
25+
26+
navigateTo(pageRoutes.login);
27+
}
28+
}
29+
30+
loading.value = false;
31+
}
32+
33+
onMounted(fetchList);
34+
</script>
35+
36+
<template>
37+
<DefaultLayout>
38+
<template #header>
39+
<div class="flex justify-between items-center mb-10">
40+
<span class="text-2xl font-bold drop-shadow-md">فرم ها</span>
41+
42+
<NuxtLink :href="pageRoutes.newForm">
43+
<ActionButton class="flex items-center justify-between p-3">
44+
<span>+</span>
45+
<span>ایجاد فرم</span>
46+
</ActionButton>
47+
</NuxtLink>
48+
</div>
49+
</template>
50+
51+
<div class="w-full h-full bg-white">
52+
<template v-if="loading"> لطفا منتظر بمانید. </template>
53+
54+
<template v-else-if="formList.length == 0">
55+
<div class="w-full h-full flex justify-center items-center">
56+
<div>
57+
هیچ فرمی تا به حال ساخته نشده است.
58+
59+
<NuxtLink class="ms-5 text-blue-700" :href="pageRoutes.newForm"
60+
>ایجاد فرم</NuxtLink
61+
>
62+
</div>
63+
</div>
64+
</template>
65+
66+
<template v-else> </template>
67+
</div>
68+
</DefaultLayout>
69+
</template>

pages/login.vue

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,16 @@
4242
>
4343
</TextInput>
4444

45-
<button
46-
type="submit"
47-
class="w-full h-12 text-center bg-[#3e3e3e] text-white rounded-xl"
48-
>
45+
<ActionButton type="submit" class="w-full h-12 text-center">
4946
ورود
50-
</button>
47+
</ActionButton>
5148
</form>
5249
</div>
5350
</template>
5451

5552
<script lang="ts" setup>
5653
import signInUp from "~/api/signInUp";
54+
import ActionButton from "~/components/ActionButton.vue";
5755
5856
definePageMeta({
5957
layout: false,

pages/new-form.vue

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script setup lang="ts">
2+
import DefaultLayout from "~/layouts/DefaultLayout.vue";
3+
</script>
4+
5+
<template>
6+
<DefaultLayout>
7+
<template #header>
8+
<div class="mb-6">
9+
<span class="text-2xl font-bold drop-shadow-md">فرم ها</span>
10+
</div>
11+
12+
<div class="w-full bg-white rounded-xl flex justify-end p-3">
13+
<ActionButton class="p-3 flex items-center">
14+
<Icon name="hugeicons:tick-01" size="20" class="me-2" />
15+
16+
<span>ذخیره فرم</span>
17+
</ActionButton>
18+
</div>
19+
</template>
20+
21+
<div class="flex flex-col items-stretch">
22+
<!-- title form -->
23+
<div class="bg-white rounded-xl flex items-stretch p-3">
24+
<div class="w-[18rem]">
25+
<TextInput label="نام فرم" placeholder="یک عنوان برای این فرم" />
26+
</div>
27+
</div>
28+
</div>
29+
</DefaultLayout>
30+
</template>

0 commit comments

Comments
 (0)