@goweekdays/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 (48) 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 +37 -0
  8. package/.playground/nuxt.config.ts +21 -0
  9. package/CHANGELOG.md +7 -0
  10. package/README.md +73 -0
  11. package/app.vue +3 -0
  12. package/components/BtnUploadFile.vue +139 -0
  13. package/components/Input/Date.vue +177 -0
  14. package/components/Input/Number.vue +102 -0
  15. package/components/Input/Password.vue +35 -0
  16. package/components/InputLabel.vue +18 -0
  17. package/components/Layout/Header.vue +271 -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/components/SpecificAttr.vue +57 -0
  25. package/composables/useAddress.ts +107 -0
  26. package/composables/useLocal.ts +53 -0
  27. package/composables/useLocalAuth.ts +117 -0
  28. package/composables/useOrg.ts +73 -0
  29. package/composables/usePaymentMethod.ts +69 -0
  30. package/composables/usePermission.ts +54 -0
  31. package/composables/useRecapPermission.ts +26 -0
  32. package/composables/useRole.ts +73 -0
  33. package/composables/useSubscription.ts +94 -0
  34. package/composables/useUser.ts +95 -0
  35. package/composables/useUtils.ts +150 -0
  36. package/composables/useVerification.ts +18 -0
  37. package/eslint.config.js +3 -0
  38. package/nuxt.config.ts +46 -0
  39. package/package.json +30 -0
  40. package/plugins/API.ts +42 -0
  41. package/plugins/vuetify.ts +44 -0
  42. package/tsconfig.json +3 -0
  43. package/types/address.d.ts +13 -0
  44. package/types/local.d.ts +43 -0
  45. package/types/org.d.ts +12 -0
  46. package/types/permission.d.ts +24 -0
  47. package/types/role.d.ts +11 -0
  48. package/types/subscription.d.ts +9 -0
@@ -0,0 +1,8 @@
1
+ # Changesets
2
+
3
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
+ with multi-package repos, or single-package repos to help you version and publish your code. You can
5
+ find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
+
7
+ We have a quick list of common questions to get you started engaging with this project in
8
+ [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "restricted",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": []
11
+ }
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_size = 2
5
+ indent_style = space
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,17 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches:
5
+ - "**"
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ node-version: 20.x
15
+ cache: "yarn"
16
+ - run: yarn install --frozen-lockfile && yarn dev:prepare
17
+ - run: yarn build
@@ -0,0 +1,39 @@
1
+ name: Publish
2
+ on:
3
+ workflow_run:
4
+ workflows: [CI]
5
+ branches: [main]
6
+ types: [completed]
7
+
8
+ concurrency: ${{ github.workflow }}-${{ github.ref }}
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ publish:
16
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-node@v4
22
+ with:
23
+ node-version: 20.x
24
+ cache: "yarn" # Use yarn for caching
25
+ cache-dependency-path: yarn.lock # Cache Yarn lockfile
26
+
27
+ - run: yarn install --immutable # Install dependencies with immutable lockfile
28
+
29
+ - name: Create .npmrc for publishing
30
+ run: |
31
+ echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ~/.npmrc
32
+
33
+ - name: Create Release Pull Request or Publish
34
+ id: changesets
35
+ uses: changesets/action@v1
36
+ with:
37
+ publish: yarn release # Use yarn for publishing
38
+ env:
39
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/.nuxtrc ADDED
@@ -0,0 +1 @@
1
+ typescript.includeWorkspace = true
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <v-app>
3
+ <LayoutHeader />
4
+
5
+ <LayoutNavigationDrawer :navigationItems="navigationItems">
6
+ </LayoutNavigationDrawer>
7
+
8
+ <v-main>
9
+ <slot />
10
+ </v-main>
11
+ </v-app>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ const navigationItems = computed(() => {
16
+ const items: Array<TNavigationItem> = [];
17
+ items.push(
18
+ {
19
+ title: "Create new request",
20
+ route: { name: "index" },
21
+ icon: "mdi-plus",
22
+ },
23
+ {
24
+ title: "Requests",
25
+ route: { name: "index" },
26
+ icon: "mdi-account-circle",
27
+ },
28
+ {
29
+ title: "Draft",
30
+ route: { name: "index" },
31
+ icon: "mdi-account-circle",
32
+ }
33
+ );
34
+
35
+ return items;
36
+ });
37
+ </script>
@@ -0,0 +1,21 @@
1
+ export default defineNuxtConfig({
2
+ extends: [".."],
3
+ modules: ["@nuxt/eslint"],
4
+ compatibilityDate: "2024-11-03",
5
+
6
+ runtimeConfig: {
7
+ public: {
8
+ cookieConfig: {
9
+ domain: (process.env.DOMAIN as string) ?? "localhost",
10
+ secure: true,
11
+ maxAge: 30 * 24 * 60 * 60,
12
+ },
13
+ APP_NAME: (process.env.APP_NAME as string) ?? "App",
14
+ APP_NAME_ROUTE: (process.env.APP_NAME_ROUTE as string) ?? "index",
15
+ APP_ACCOUNT: (process.env.APP_ACCOUNT as string) ?? "",
16
+ S3_BUCKET_ENDPOINT: (process.env.S3_BUCKET_ENDPOINT as string) ?? "",
17
+ APP_ADMIN: (process.env.APP_ADMIN as string) ?? "",
18
+ APP_CBA_RECAP: (process.env.APP_CBA_RECAP as string) ?? "",
19
+ },
20
+ },
21
+ });
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @goweekdays/layer-common
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 940da0b: Init
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Nuxt Layer Starter
2
+
3
+ Create Nuxt extendable layer with this GitHub template.
4
+
5
+ ## Setup
6
+
7
+ Make sure to install the dependencies:
8
+
9
+ ```bash
10
+ pnpm install
11
+ ```
12
+
13
+ ## Working on your layer
14
+
15
+ Your layer is at the root of this repository, it is exactly like a regular Nuxt project, except you can publish it on NPM.
16
+
17
+ The `.playground` directory should help you on trying your layer during development.
18
+
19
+ Running `pnpm dev` will prepare and boot `.playground` directory, which imports your layer itself.
20
+
21
+ ## Distributing your layer
22
+
23
+ Your Nuxt layer is shaped exactly the same as any other Nuxt project, except you can publish it on NPM.
24
+
25
+ To do so, you only have to check if `files` in `package.json` are valid, then run:
26
+
27
+ ```bash
28
+ npm publish --access public
29
+ ```
30
+
31
+ Once done, your users will only have to run:
32
+
33
+ ```bash
34
+ npm install --save your-layer
35
+ ```
36
+
37
+ Then add the dependency to their `extends` in `nuxt.config`:
38
+
39
+ ```ts
40
+ defineNuxtConfig({
41
+ extends: 'your-layer'
42
+ })
43
+ ```
44
+
45
+ ## Development Server
46
+
47
+ Start the development server on http://localhost:3000
48
+
49
+ ```bash
50
+ pnpm dev
51
+ ```
52
+
53
+ ## Production
54
+
55
+ Build the application for production:
56
+
57
+ ```bash
58
+ pnpm build
59
+ ```
60
+
61
+ Or statically generate it with:
62
+
63
+ ```bash
64
+ pnpm generate
65
+ ```
66
+
67
+ Locally preview production build:
68
+
69
+ ```bash
70
+ pnpm preview
71
+ ```
72
+
73
+ Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
package/app.vue ADDED
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <h1>Common</h1>
3
+ </template>
@@ -0,0 +1,139 @@
1
+ <template>
2
+ <v-input
3
+ :label="label"
4
+ :error="!!errorMessage"
5
+ :error-messages="errorMessage"
6
+ :hide-details="readOnly ?? !!value"
7
+ class="btn-input-file"
8
+ >
9
+ <template #default>
10
+ <v-row no-gutters>
11
+ <v-col cols="12" v-if="value || readOnly">
12
+ <v-row no-gutters>
13
+ <v-card
14
+ variant="outlined"
15
+ border="thin"
16
+ class="px-4 text-caption font-weight-medium"
17
+ :rounded="readOnly ? '' : '0 s'"
18
+ @click="emit('click:view')"
19
+ height="36"
20
+ >
21
+ <v-row no-gutters class="fill-height" align="center">
22
+ {{ value ? "View attachment" : "No file attached" }}
23
+ </v-row>
24
+ </v-card>
25
+ <v-card
26
+ v-if="!readOnly"
27
+ variant="outlined"
28
+ border="s-0 thin"
29
+ rounded="0 e"
30
+ class="px-4 d-inline"
31
+ @click="emit('click:close')"
32
+ height="36"
33
+ >
34
+ <v-row no-gutters class="fill-height" align="center">
35
+ <v-icon size="small"> mdi-close </v-icon>
36
+ </v-row>
37
+ </v-card>
38
+ </v-row>
39
+ </v-col>
40
+
41
+ <v-col cols="12" v-else>
42
+ <v-btn
43
+ variant="outlined"
44
+ border="thin"
45
+ class="text-none"
46
+ :prepend-icon="value ? '' : 'mdi-tray-arrow-up'"
47
+ @click="selectAttachment"
48
+ :disabled="loading"
49
+ :loading="loading"
50
+ >
51
+ Add file
52
+ </v-btn>
53
+ </v-col>
54
+ </v-row>
55
+
56
+ <v-file-input
57
+ v-show="false"
58
+ v-model="attachment"
59
+ multiple
60
+ accept="image/*"
61
+ :class="`attachment-input-${inputClass}`"
62
+ @change="uploadAttachment"
63
+ :name="name + 'custom-input-file'"
64
+ ></v-file-input>
65
+ </template>
66
+ </v-input>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ const value = defineModel<string>({ required: true, default: "" });
71
+ const attachment = ref<File | null>(null);
72
+ const loading = ref(false);
73
+
74
+ const emit = defineEmits(["click:view", "click:close", "upload"]);
75
+
76
+ const { addFile } = useFile();
77
+ const inputClass = Date.now().toString();
78
+
79
+ function selectAttachment() {
80
+ const input = document.querySelector(
81
+ `.attachment-input-${inputClass} input`
82
+ ) as HTMLInputElement;
83
+ input.click();
84
+ }
85
+
86
+ async function uploadAttachment(e: Event) {
87
+ loading.value = true;
88
+ const files = (e.target as HTMLInputElement).files;
89
+
90
+ if (files && files.length > 0) {
91
+ const file = files[0];
92
+
93
+ try {
94
+ const res = await addFile(file);
95
+ value.value = res.id as string;
96
+ await emit("upload", res);
97
+ } catch (error) {
98
+ console.log(error);
99
+ }
100
+ } else {
101
+ console.log("No file selected");
102
+ }
103
+ loading.value = false;
104
+ }
105
+
106
+ // Other props (optional)
107
+ const { label, rules, name, readOnly } = defineProps({
108
+ label: {
109
+ type: String,
110
+ default: "Rating",
111
+ },
112
+ // Array of validation functions: (number) => true | string
113
+ rules: {
114
+ type: Array<Function>,
115
+ default: () => [],
116
+ },
117
+ name: {
118
+ type: String,
119
+ required: true,
120
+ default: "button-upload-file",
121
+ },
122
+ readOnly: {
123
+ type: Boolean,
124
+ default: false,
125
+ },
126
+ });
127
+
128
+ /**
129
+ * Compute the first failed validation rule, if any.
130
+ * Each rule returns true (if valid) or a string (if invalid).
131
+ */
132
+ const errorMessage = computed(() => {
133
+ for (const rule of rules) {
134
+ const result = rule(attachment.value || value.value);
135
+ return typeof result === "string" ? result : null;
136
+ }
137
+ return null;
138
+ });
139
+ </script>
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <v-input
3
+ :error="!!validationErrMsg"
4
+ :error-messages="validationErrMsg"
5
+ hide-details="auto"
6
+ :name="name + 'custom-input-date'"
7
+ :disabled="props.disabled"
8
+ >
9
+ <template #default>
10
+ <v-row>
11
+ <v-col cols="12" lg="6" md="6">
12
+ <v-select
13
+ v-model="month"
14
+ :items="props.months"
15
+ label="Month"
16
+ density="comfortable"
17
+ :name="props.name + 'custom-input-date-month'"
18
+ hide-details
19
+ :error="!!validationErrMsg"
20
+ :disabled="props.disabled"
21
+ />
22
+ </v-col>
23
+
24
+ <v-col cols="12" lg="3" md="3">
25
+ <v-text-field
26
+ v-model.number="day"
27
+ type="number"
28
+ label="Day"
29
+ hide-details
30
+ placeholder="DD"
31
+ density="comfortable"
32
+ :name="props.name + 'custom-input-date-day'"
33
+ :error="!!validationErrMsg"
34
+ :disabled="props.disabled"
35
+ />
36
+ </v-col>
37
+
38
+ <v-col cols="12" lg="3" md="3">
39
+ <v-text-field
40
+ v-model.number="year"
41
+ type="number"
42
+ label="Year"
43
+ hide-details
44
+ placeholder="YYYY"
45
+ density="comfortable"
46
+ :name="props.name + 'custom-input-date-year'"
47
+ :error="!!validationErrMsg"
48
+ :disabled="props.disabled"
49
+ />
50
+ </v-col>
51
+ </v-row>
52
+ </template>
53
+ </v-input>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
+ const month = defineModel("month", { default: "" });
58
+ const day = defineModel("day", { default: 0 });
59
+ const year = defineModel("year", { default: 0 });
60
+
61
+ const props = defineProps({
62
+ months: {
63
+ type: Array,
64
+ default: [
65
+ "January",
66
+ "February",
67
+ "March",
68
+ "April",
69
+ "May",
70
+ "June",
71
+ "July",
72
+ "August",
73
+ "September",
74
+ "October",
75
+ "November",
76
+ "December",
77
+ ],
78
+ },
79
+ name: {
80
+ type: String,
81
+ default: "input-date-combo",
82
+ },
83
+ disabled: {
84
+ type: Boolean,
85
+ default: false,
86
+ },
87
+ });
88
+
89
+ const isLeapYear = (year: number): boolean =>
90
+ (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
91
+
92
+ const monthMap: Record<string, number> = {
93
+ January: 1,
94
+ February: 2,
95
+ March: 3,
96
+ April: 4,
97
+ May: 5,
98
+ June: 6,
99
+ July: 7,
100
+ August: 8,
101
+ September: 9,
102
+ October: 10,
103
+ November: 11,
104
+ December: 12,
105
+ };
106
+
107
+ const validateBirthDate = (): string => {
108
+ const _month = monthMap[month.value];
109
+ const _day = day.value;
110
+ const _year = year.value;
111
+
112
+ const errorMessage = "Enter a valid date";
113
+
114
+ if (
115
+ !_month ||
116
+ !_day ||
117
+ !_year ||
118
+ _year < 1904 ||
119
+ _year > new Date().getFullYear()
120
+ ) {
121
+ return errorMessage;
122
+ }
123
+
124
+ const daysInMonth = [
125
+ 31,
126
+ isLeapYear(_year) ? 29 : 28,
127
+ 31,
128
+ 30,
129
+ 31,
130
+ 30,
131
+ 31,
132
+ 31,
133
+ 30,
134
+ 31,
135
+ 30,
136
+ 31,
137
+ ];
138
+
139
+ if (_day < 1 || _day > daysInMonth[_month - 1]) {
140
+ return errorMessage;
141
+ }
142
+
143
+ if (!_month || !_day || !_year) {
144
+ return errorMessage;
145
+ }
146
+
147
+ return "";
148
+ };
149
+
150
+ const validationErrMsg = ref("");
151
+
152
+ const date = computed(() => {
153
+ return {
154
+ day: day.value,
155
+ month: month.value,
156
+ year: year.value,
157
+ };
158
+ });
159
+
160
+ validationErrMsg.value = validateBirthDate();
161
+
162
+ const disabled = computed(() => props.disabled);
163
+
164
+ watch(disabled, (curr) => {
165
+ if (curr) {
166
+ validationErrMsg.value = validateBirthDate();
167
+ }
168
+ });
169
+
170
+ watch(
171
+ date,
172
+ () => {
173
+ validationErrMsg.value = validateBirthDate();
174
+ },
175
+ { deep: true }
176
+ );
177
+ </script>
@@ -0,0 +1,102 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, nextTick, useAttrs } from "vue";
3
+
4
+ const modelValue = defineModel<number>(); // Store actual number
5
+ const inputRef = ref<HTMLInputElement | null>(null); // Reference input field
6
+ const attrs = useAttrs(); // Get all passed attributes (rules, required, etc.)
7
+
8
+ let cursorPosition = 0; // Track cursor position
9
+ let forceCursorToEnd = false; // Track if cursor should be forced to end
10
+
11
+ // Computed property to format value with commas
12
+ const formattedValue = computed({
13
+ get: () =>
14
+ modelValue.value !== undefined ? modelValue.value.toLocaleString() : "",
15
+ set: (val: string) => {
16
+ const rawValue = val.replace(/\D/g, ""); // Remove non-numeric characters
17
+ const numericValue = rawValue ? Number(rawValue) : 0;
18
+
19
+ if (!isNaN(numericValue)) {
20
+ modelValue.value = numericValue;
21
+ nextTick(() => restoreCursor());
22
+ }
23
+ },
24
+ });
25
+
26
+ // Handle keydown for navigation & number changes
27
+ const handleKeyDown = (event: KeyboardEvent) => {
28
+ if (!inputRef.value || modelValue.value === undefined) return;
29
+
30
+ const { selectionStart, selectionEnd, value } = inputRef.value;
31
+ const isAllSelected = selectionStart === 0 && selectionEnd === value.length;
32
+
33
+ // Allow only numbers (prevent non-numeric input)
34
+ if (
35
+ !/^\d$/.test(event.key) &&
36
+ ![
37
+ "Backspace",
38
+ "Delete",
39
+ "ArrowLeft",
40
+ "ArrowRight",
41
+ "ArrowUp",
42
+ "ArrowDown",
43
+ ].includes(event.key)
44
+ ) {
45
+ event.preventDefault();
46
+ return;
47
+ }
48
+
49
+ // If all text is selected and a number is pressed, replace entire value
50
+ if (isAllSelected && /^\d$/.test(event.key)) {
51
+ event.preventDefault();
52
+ modelValue.value = Number(event.key);
53
+ forceCursorToEnd = true;
54
+ nextTick(() => restoreCursor());
55
+ return;
56
+ }
57
+
58
+ // Allow cursor movement with ArrowLeft & ArrowRight
59
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
60
+ forceCursorToEnd = false;
61
+ return;
62
+ }
63
+
64
+ // Force cursor to end when incrementing/decrementing
65
+ if (event.key === "ArrowUp") {
66
+ event.preventDefault();
67
+ modelValue.value += 1;
68
+ forceCursorToEnd = true;
69
+ } else if (event.key === "ArrowDown") {
70
+ event.preventDefault();
71
+ modelValue.value = Math.max(0, modelValue.value - 1);
72
+ forceCursorToEnd = true;
73
+ } else {
74
+ cursorPosition = selectionStart || 0; // Store cursor position before input
75
+ }
76
+
77
+ nextTick(() => restoreCursor());
78
+ };
79
+
80
+ // Restore cursor position correctly
81
+ const restoreCursor = () => {
82
+ if (!inputRef.value) return;
83
+ const length = formattedValue.value.length;
84
+
85
+ if (forceCursorToEnd) {
86
+ inputRef.value.setSelectionRange(length, length); // Keep cursor at end
87
+ } else {
88
+ inputRef.value.setSelectionRange(cursorPosition, cursorPosition); // Keep normal position
89
+ }
90
+ };
91
+ </script>
92
+
93
+ <template>
94
+ <v-text-field
95
+ ref="inputRef"
96
+ v-model="formattedValue"
97
+ v-bind="attrs"
98
+ type="text"
99
+ placeholder="Enter a number"
100
+ @keydown="handleKeyDown"
101
+ ></v-text-field>
102
+ </template>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <v-text-field
3
+ id="_password"
4
+ :model-value="modelValue"
5
+ :type="showPassword ? 'text' : 'password'"
6
+ :append-inner-icon="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
7
+ @click:append-inner="togglePasswordVisibility"
8
+ @update:model-value="updatePassword"
9
+ v-bind="$attrs"
10
+ autocomplete="off"
11
+ />
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ // Define the props for the v-model
16
+ defineProps({
17
+ modelValue: {
18
+ type: String,
19
+ },
20
+ });
21
+
22
+ // Define emit for updating v-model
23
+ const emit = defineEmits(["update:modelValue"]);
24
+
25
+ // Control password visibility
26
+ const showPassword = ref(false);
27
+ const togglePasswordVisibility = () => {
28
+ showPassword.value = !showPassword.value;
29
+ };
30
+
31
+ // Handle input update and emit to parent
32
+ const updatePassword = (value: string) => {
33
+ emit("update:modelValue", value);
34
+ };
35
+ </script>