@iservice365/layer-common 0.0.1

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.
Files changed (39) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.editorconfig +12 -0
  4. package/.github/workflows/main.yml +17 -0
  5. package/.github/workflows/publish.yml +39 -0
  6. package/.nuxtrc +1 -0
  7. package/.playground/app.vue +36 -0
  8. package/.playground/eslint.config.mjs +6 -0
  9. package/.playground/nuxt.config.ts +22 -0
  10. package/CHANGELOG.md +8 -0
  11. package/README.md +73 -0
  12. package/app.vue +3 -0
  13. package/components/BtnUploadFile.vue +139 -0
  14. package/components/Input/Date.vue +177 -0
  15. package/components/Input/Password.vue +22 -0
  16. package/components/InputLabel.vue +18 -0
  17. package/components/Layout/Header.vue +248 -0
  18. package/components/Layout/NavigationDrawer.vue +24 -0
  19. package/components/ListItem.vue +35 -0
  20. package/components/LocalPagination.vue +31 -0
  21. package/components/NavigationItem.vue +59 -0
  22. package/components/PlaceholderComponent.vue +34 -0
  23. package/components/Snackbar.vue +23 -0
  24. package/composables/useLocal.ts +41 -0
  25. package/composables/useLocalAuth.ts +109 -0
  26. package/composables/useOrg.ts +127 -0
  27. package/composables/usePermission.ts +54 -0
  28. package/composables/useRecapPermission.ts +26 -0
  29. package/composables/useRole.ts +73 -0
  30. package/composables/useUser.ts +93 -0
  31. package/composables/useUtils.ts +109 -0
  32. package/nuxt.config.ts +60 -0
  33. package/package.json +33 -0
  34. package/plugins/API.ts +44 -0
  35. package/plugins/vuetify.ts +41 -0
  36. package/tsconfig.json +3 -0
  37. package/types/local.d.ts +63 -0
  38. package/types/permission.d.ts +24 -0
  39. package/types/role.d.ts +11 -0
@@ -0,0 +1,248 @@
1
+ <template>
2
+ <v-app-bar scroll-behavior="elevate" scroll-threshold="200">
3
+ <div style="width: 264px" class="ml-2">
4
+ <nuxt-link
5
+ class="text-h6 font-weight-medium text-decoration-none APP_NAME"
6
+ :to="{ name: APP_NAME_ROUTE }"
7
+ >
8
+ {{ APP_NAME }}
9
+ </nuxt-link>
10
+ </div>
11
+
12
+ <v-row no-gutters>
13
+ <v-col cols="6">
14
+ <v-text-field
15
+ v-model="search"
16
+ width="100%"
17
+ prepend-inner-icon="mdi-magnify"
18
+ variant="solo-filled"
19
+ flat
20
+ density="comfortable"
21
+ hide-details
22
+ rounded="xl"
23
+ />
24
+ </v-col>
25
+ </v-row>
26
+
27
+ <template #append>
28
+ <v-btn
29
+ icon="mdi-theme-light-dark"
30
+ variant="text"
31
+ class="mx-2"
32
+ @click="toggleTheme"
33
+ />
34
+
35
+ <v-menu offset="10px" :close-on-content-click="false">
36
+ <template #activator="{ props }">
37
+ <v-btn icon="mdi-dots-grid" variant="text" v-bind="props" />
38
+ </template>
39
+
40
+ <v-card
41
+ width="380"
42
+ max-height="400px"
43
+ elevation="2"
44
+ rounded="xl"
45
+ class="pa-4"
46
+ >
47
+ <v-row no-gutters>
48
+ <v-col cols="12">
49
+ <v-card width="100%" variant="tonal" rounded="t-xl b-0">
50
+ <v-row class="pa-4">
51
+ <v-col cols="4">
52
+ <v-btn
53
+ prepend-icon="mdi-account"
54
+ stacked
55
+ rounded="xl"
56
+ variant="text"
57
+ class="text-capitalize text-subtitle-2"
58
+ @click="redirect(APP_ACCOUNT, 'home')"
59
+ >
60
+ Account
61
+ </v-btn>
62
+ </v-col>
63
+
64
+ <v-col cols="4">
65
+ <v-btn
66
+ prepend-icon="mdi-security"
67
+ stacked
68
+ rounded="xl"
69
+ variant="text"
70
+ class="text-capitalize text-subtitle-2"
71
+ @click="redirect(APP_ADMIN, 'home')"
72
+ >
73
+ Admin
74
+ </v-btn>
75
+ </v-col>
76
+ </v-row>
77
+ </v-card>
78
+ </v-col>
79
+
80
+ <v-col cols=" 12" class="mt-2">
81
+ <v-card
82
+ width="100%"
83
+ min-height="50px"
84
+ variant="tonal"
85
+ rounded="b-xl"
86
+ >
87
+ <v-card-title class="text-center pb-0"> Apps </v-card-title>
88
+
89
+ <v-row no-gutters class="px-2 pb-2">
90
+ <v-col
91
+ v-for="item in apps"
92
+ :key="item.title"
93
+ cols="4"
94
+ class="pa-2"
95
+ >
96
+ <v-btn
97
+ stacked
98
+ width="100%"
99
+ :prepend-icon="item.icon"
100
+ rounded="xl"
101
+ variant="text"
102
+ class="text-center text-capitalize text-caption"
103
+ @click="redirect(item.link, item.landingPage)"
104
+ >
105
+ {{ item.title }}
106
+ </v-btn>
107
+ </v-col>
108
+ </v-row>
109
+ </v-card>
110
+ </v-col>
111
+ </v-row>
112
+ </v-card>
113
+ </v-menu>
114
+
115
+ <v-menu offset="10px" :close-on-content-click="false">
116
+ <template #activator="{ props }">
117
+ <v-btn fab variant="text" icon v-bind="props" class="mx-2">
118
+ <v-avatar color="surface-variant" size="42">
119
+ <v-img
120
+ v-if="currentUser?.profile"
121
+ :src="profile"
122
+ width="42"
123
+ height="42"
124
+ />
125
+
126
+ <span v-else class="text-h5">{{ getNameInitials(name) }}</span>
127
+ </v-avatar>
128
+ </v-btn>
129
+ </template>
130
+
131
+ <v-card
132
+ width="350"
133
+ max-height="600px"
134
+ elevation="2"
135
+ rounded="xl"
136
+ class="pa-4"
137
+ >
138
+ <v-row no-gutters>
139
+ <v-col cols="12">
140
+ <v-row no-gutters justify="center">
141
+ <v-avatar color="surface-variant" size="75">
142
+ <v-img
143
+ v-if="currentUser?.profile"
144
+ :src="profile"
145
+ width="75"
146
+ height="75"
147
+ />
148
+
149
+ <span v-else class="text-h5">{{
150
+ getNameInitials(name)
151
+ }}</span>
152
+ </v-avatar>
153
+ </v-row>
154
+ </v-col>
155
+
156
+ <v-col cols="12" class="text-center mt-2 mb-4">
157
+ {{ currentUser?.firstName }} {{ currentUser?.lastName }}
158
+ </v-col>
159
+
160
+ <v-col cols="12">
161
+ <v-btn
162
+ block
163
+ rounded="xl"
164
+ variant="tonal"
165
+ size="x-large"
166
+ class="text-none text-subtitle-1 font-weight-regular"
167
+ @click="logout()"
168
+ >
169
+ Logout
170
+ </v-btn>
171
+ </v-col>
172
+ </v-row>
173
+ </v-card>
174
+ </v-menu>
175
+ </template>
176
+ </v-app-bar>
177
+ </template>
178
+
179
+ <script setup lang="ts">
180
+ import { useTheme } from "vuetify";
181
+
182
+ const search = defineModel("search", { type: String });
183
+
184
+ const { redirect, apps } = useLocal();
185
+
186
+ const { APP_ACCOUNT, APP_ADMIN, APP_NAME, APP_NAME_ROUTE } =
187
+ useRuntimeConfig().public;
188
+
189
+ const theme = useTheme();
190
+
191
+ function toggleTheme() {
192
+ theme.global.name.value = theme.global.current.value.dark ? "light" : "dark";
193
+ }
194
+
195
+ const { currentUser } = useLocalAuth();
196
+
197
+ const profile = computed(() => {
198
+ return `/api/public/${currentUser.value?.profile}`;
199
+ });
200
+
201
+ function logout() {
202
+ if (APP_NAME.toLowerCase() !== "account") {
203
+ redirect(`${APP_ACCOUNT}/logout`);
204
+ return;
205
+ }
206
+
207
+ useRouter().push({ name: "logout" });
208
+ }
209
+
210
+ const name = computed(() => {
211
+ let name = "";
212
+ if (currentUser.value?.firstName) {
213
+ name = currentUser.value.firstName;
214
+ }
215
+
216
+ if (currentUser.value?.lastName) {
217
+ name += ` ${currentUser.value.lastName}`;
218
+ }
219
+
220
+ return name;
221
+ });
222
+
223
+ const { getNameInitials } = useUtils();
224
+ </script>
225
+
226
+ <style scoped>
227
+ .APP_NAME {
228
+ display: inline-block;
229
+ word-wrap: break-word;
230
+ white-space: normal;
231
+ text-align: center;
232
+ color: unset !important;
233
+ }
234
+
235
+ .open-list {
236
+ transition: transform 0.2s ease-in-out;
237
+ transform: rotate(180deg);
238
+ }
239
+
240
+ .close-list {
241
+ transition: transform 0.3s ease-in-out;
242
+ transform: rotate(0deg);
243
+ }
244
+
245
+ .v-list-group__items .v-list-item {
246
+ padding-inline-start: 32px !important;
247
+ }
248
+ </style>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <v-navigation-drawer permanent floating class="pr-2">
3
+ <v-list>
4
+ <slot name="action"/>
5
+ <template
6
+ v-for="(navigationItem, navigationIndex) in props.navigationItems"
7
+ :key="`${navigationItem.route.name}-${navigationIndex}`"
8
+ >
9
+ <NavigationItem
10
+ :title="navigationItem.title"
11
+ :icon="navigationItem.icon"
12
+ :route="navigationItem.route"
13
+ :children="navigationItem.children"
14
+ />
15
+ </template>
16
+ </v-list>
17
+ </v-navigation-drawer>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ const props = defineProps({
22
+ navigationItems: { type: Array<TNavigationItem>, required: true },
23
+ });
24
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-list-item :class="props.divider ? border : defaultBorder">
3
+ <v-row>
4
+ <v-col cols="4" class="text-subtitle-2">
5
+ <slot name="title"> List Title </slot>
6
+ </v-col>
7
+
8
+ <v-col cols="8" class="text-subtitle-2">
9
+ <slot name="value"> value </slot>
10
+ </v-col>
11
+ </v-row>
12
+
13
+ <template #append>
14
+ <slot name="append">
15
+ <v-icon size="30">{{ props.icon }}</v-icon>
16
+ </slot>
17
+ </template>
18
+ </v-list-item>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ const props = defineProps({
23
+ divider: {
24
+ type: Boolean,
25
+ default: true,
26
+ },
27
+ icon: {
28
+ type: String,
29
+ default: "mdi-chevron-right",
30
+ },
31
+ });
32
+
33
+ const defaultBorder = "pa-0 pl-8 pr-6 py-4";
34
+ const border = "border-b-sm" + " " + defaultBorder;
35
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div class="arrow-navigation">
3
+ <v-btn icon="mdi-chevron-left" variant="text" density="comfortable" :disabled="page <= 1" @click="decrement" />
4
+ <v-btn
5
+ icon="mdi-chevron-right" variant="text" density="comfortable" :disabled="page >= props.length"
6
+ @click="increment" />
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ const page = defineModel({ type: Number, default: 0 });
12
+ function increment() {
13
+ page.value++;
14
+ }
15
+
16
+ function decrement() {
17
+ page.value--;
18
+ }
19
+ const emit = defineEmits(['update:value']);
20
+ watch(page, () => {
21
+ emit('update:value', page.value);
22
+ });
23
+
24
+ const props = defineProps({
25
+ length: {
26
+ type: Number,
27
+ required: true,
28
+ default: 0
29
+ }
30
+ });
31
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <v-list-group v-if="children && children.length">
3
+ <template #activator="{ props }">
4
+ <v-list-item
5
+ v-bind="props"
6
+ :prepend-icon="icon"
7
+ rounded="e-pill"
8
+ class="text-subtitle-2"
9
+ >
10
+ {{ title }}
11
+ </v-list-item>
12
+ </template>
13
+
14
+ <NavigationItem
15
+ v-for="(child, childIndex) in children"
16
+ :key="`${child.title}-${childIndex}`"
17
+ :title="child.title"
18
+ :icon="child.icon"
19
+ :route="child.route"
20
+ :children="child.children"
21
+ />
22
+ </v-list-group>
23
+
24
+ <v-list-item
25
+ v-else
26
+ :prepend-icon="icon"
27
+ :to="props.route"
28
+ rounded="e-pill"
29
+ class="text-subtitle-2"
30
+ >
31
+ {{ title }}
32
+ </v-list-item>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ const props = defineProps({
37
+ title: {
38
+ type: String,
39
+ required: true,
40
+ default: "Title",
41
+ },
42
+ icon: {
43
+ type: String,
44
+ required: false,
45
+ default: "",
46
+ },
47
+ route: {
48
+ type: Object,
49
+ default() {
50
+ return { name: "", params: {} };
51
+ },
52
+ },
53
+ children: {
54
+ type: Array<TNavigationItem>,
55
+ required: false,
56
+ default: () => [],
57
+ },
58
+ });
59
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div class="maintenance-container">
3
+ <h1>🚧 Page Under Maintenance</h1>
4
+ <p>
5
+ We're currently working on improving this page to serve you better. Please
6
+ check back soon!
7
+ </p>
8
+ <p>Thank you for your patience.</p>
9
+ </div>
10
+ </template>
11
+
12
+ <style scoped>
13
+ .maintenance-container {
14
+ display: flex;
15
+ flex-direction: column;
16
+ justify-content: center;
17
+ align-items: center;
18
+ text-align: center;
19
+ min-height: 50vh;
20
+ margin: 0;
21
+ font-family: Arial, sans-serif;
22
+ color: #333;
23
+ }
24
+
25
+ h1 {
26
+ font-size: 2rem;
27
+ margin-bottom: 1rem;
28
+ }
29
+
30
+ p {
31
+ font-size: 1.1rem;
32
+ line-height: 1.5;
33
+ }
34
+ </style>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <v-snackbar v-model="snackbar" :color="props.color">
3
+ {{ props.text }}
4
+
5
+ <template #actions>
6
+ <v-btn variant="text" @click="snackbar = false"> Close </v-btn>
7
+ </template>
8
+ </v-snackbar>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ const snackbar = defineModel({ type: Boolean });
13
+ const props = defineProps({
14
+ text: {
15
+ type: String,
16
+ default: "",
17
+ },
18
+ color: {
19
+ type: String,
20
+ default: "primary",
21
+ },
22
+ });
23
+ </script>
@@ -0,0 +1,41 @@
1
+ export default function useLocal() {
2
+ const appConfig = useRuntimeConfig().public;
3
+
4
+ const { cookieConfig } = appConfig;
5
+
6
+ function getUserFromCookie() {
7
+ return useCookie("user", cookieConfig).value;
8
+ }
9
+
10
+ const drawer = useState("drawer", () => true);
11
+
12
+ const { APP_RECAP } = appConfig;
13
+
14
+ const apps = computed(() => {
15
+ return [
16
+ {
17
+ title: "RECAP",
18
+ icon: "mdi-account-group",
19
+ link: APP_RECAP as string,
20
+ landingPage: "requests/status/draft",
21
+ },
22
+ ];
23
+ });
24
+
25
+ function redirect(link: string, page?: string) {
26
+ const href = page ? `${link}/${page}` : link;
27
+
28
+ window.location.href = href;
29
+ }
30
+
31
+ const headerSearch = useState("headerSearch", () => "");
32
+
33
+ return {
34
+ cookieConfig,
35
+ getUserFromCookie,
36
+ drawer,
37
+ apps,
38
+ redirect,
39
+ headerSearch,
40
+ };
41
+ }
@@ -0,0 +1,109 @@
1
+ export default function useLocalAuth() {
2
+ const { cookieConfig } = useRuntimeConfig().public;
3
+
4
+ async function login({ email = "", password = "" }) {
5
+ return useNuxtApp().$api<TKeyValuePair>("/api/auth", {
6
+ method: "POST",
7
+ body: JSON.stringify({ email, password }),
8
+ });
9
+ }
10
+
11
+ function setToken({
12
+ refreshToken = "",
13
+ accessToken = "",
14
+ user = "",
15
+ org = "",
16
+ }) {
17
+ useCookie("accessToken", cookieConfig).value = accessToken;
18
+ useCookie("refreshToken", cookieConfig).value = refreshToken;
19
+ useCookie("user", cookieConfig).value = user;
20
+ useCookie("org", cookieConfig).value = org;
21
+ }
22
+
23
+ function clearCookies() {
24
+ useCookie("accessToken", cookieConfig).value = null;
25
+ useCookie("refreshToken", cookieConfig).value = null;
26
+ useCookie("user", cookieConfig).value = null;
27
+ useCookie("organization", cookieConfig).value = null;
28
+ }
29
+
30
+ async function logout() {
31
+ const refreshToken = useCookie("refreshToken", cookieConfig).value;
32
+ if (refreshToken) {
33
+ try {
34
+ await useNuxtApp().$api(`/api/auth/${refreshToken}`, {
35
+ method: "DELETE",
36
+ });
37
+
38
+ clearCookies();
39
+ } catch (error) {
40
+ console.error("Logout failed:", error);
41
+ }
42
+ }
43
+ }
44
+
45
+ const currentUser = useState((): TUser | null => null);
46
+
47
+ async function getCurrentUser() {
48
+ const user = useCookie("user", cookieConfig).value;
49
+ if (!user) return null;
50
+ const _user = await useNuxtApp().$api<TUser>(`/api/users/id/${user}`, {
51
+ method: "GET",
52
+ });
53
+ currentUser.value = _user;
54
+ return _user;
55
+ }
56
+
57
+ async function forgotPassword(email: string) {
58
+ if (!email) {
59
+ throw new Error("Email is required for password reset request.");
60
+ }
61
+
62
+ try {
63
+ const response = await useNuxtApp().$api("/api/auth/forget-password", {
64
+ method: "POST",
65
+ body: JSON.stringify({ email }),
66
+ headers: { "Content-Type": "application/json" },
67
+ });
68
+ return response;
69
+ } catch (error) {
70
+ console.error("Error in password reset request:", error);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ async function resetPassword(
76
+ otp: string,
77
+ newPassword: string,
78
+ passwordConfirmation: string
79
+ ) {
80
+ try {
81
+ return await useNuxtApp().$api("/api/auth/reset-password", {
82
+ method: "POST",
83
+ body: JSON.stringify({ otp, newPassword, passwordConfirmation }),
84
+ headers: { "Content-Type": "application/json" },
85
+ });
86
+ } catch (error) {
87
+ console.error("Error resetting password:", error);
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ function verify(id: string) {
93
+ return useNuxtApp().$api<TKeyValuePair>(`/api/auth/verify/${id}`, {
94
+ method: "GET",
95
+ });
96
+ }
97
+
98
+ return {
99
+ login,
100
+ logout,
101
+ clearCookies,
102
+ getCurrentUser,
103
+ setToken,
104
+ forgotPassword,
105
+ resetPassword,
106
+ currentUser,
107
+ verify,
108
+ };
109
+ }