@iservice365/layer-common 1.0.0 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4d92608: Vehicle mgmt - initial release
8
+
3
9
  ## 1.0.0
4
10
 
5
11
  ### Major Changes
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div>
3
+ <v-text-field ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly" @click="openDatePicker" placeholder="MM/DD/YYYY, HH:MM AM/PM" :rules="rules">
4
+ <template #append-inner>
5
+ <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
6
+ </template>
7
+ </v-text-field>
8
+ <div class="w-100 d-flex align-end ga-3 hidden-input">
9
+ <input ref="dateInput" type="datetime-local" v-model="dateTime" />
10
+ </div>
11
+
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { ref, computed, watch } from 'vue'
17
+
18
+ const prop = defineProps({
19
+ rules: {
20
+ type: Array as PropType<Array<any>>,
21
+ default: () => []
22
+ }
23
+ })
24
+
25
+ const dateTime = defineModel<string | null>({ default: null })
26
+
27
+ const dateTimeFormattedReadOnly = ref<string | null>(null)
28
+
29
+
30
+
31
+ const dateInput = ref<HTMLInputElement | null>(null)
32
+ const dateTimePickerRef = ref<HTMLInputElement | null>(null)
33
+
34
+ function openDatePicker() {
35
+ dateInput.value?.showPicker()
36
+ }
37
+
38
+ function validate(){
39
+ (dateTimePickerRef.value as any)?.validate()
40
+ }
41
+
42
+
43
+ watch(dateTime, (dateVal) => {
44
+ if (!dateVal) return dateTimeFormattedReadOnly.value = null
45
+ const date = new Date(dateVal)
46
+ const options: Intl.DateTimeFormatOptions = {
47
+ year: 'numeric',
48
+ month: '2-digit',
49
+ day: '2-digit',
50
+ hour: '2-digit',
51
+ minute: '2-digit',
52
+ hour12: true
53
+ }
54
+ const formatted = date.toLocaleString('en-US', options)
55
+ dateTimeFormattedReadOnly.value = formatted
56
+ })
57
+
58
+
59
+
60
+ defineExpose({
61
+ validate
62
+ })
63
+ </script>
64
+
65
+ <style scoped>
66
+ .hidden-input {
67
+ opacity: 0;
68
+ height: 0;
69
+ width: 0;
70
+ }
71
+ </style>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <v-row dense>
3
+ <!-- Country/Dial Code -->
4
+ <v-col cols="4">
5
+ <v-select
6
+ v-model="localDialCode"
7
+ :items="countryOptions"
8
+ item-title="display"
9
+ item-value="label"
10
+ density="comfortable"
11
+ :rules="[dialCodeRequired]"
12
+ hide-details="auto"
13
+ placeholder="Select Country"
14
+ z-index="100000000"
15
+ :menu-props="{
16
+ maxHeight: 300,
17
+ }"
18
+ no-data-text="No countries available"
19
+ >
20
+ <template #selection="{ item }">
21
+ <span class="text-body-2">{{ item.raw.dialCode }}</span>
22
+ </template>
23
+ <template #item="{ item }">
24
+ <v-list-item-title>
25
+ {{ item.raw.display }}
26
+ </v-list-item-title>
27
+ </template>
28
+ </v-select>
29
+ </v-col>
30
+
31
+ <!-- Phone Number -->
32
+ <v-col cols="8">
33
+ <v-text-field
34
+ v-model="localPhoneNumber"
35
+ :placeholder="selectedCountry?.placeholder || 'Phone Number'"
36
+ density="comfortable"
37
+ :rules="phoneValidationRules"
38
+ hide-details="auto"
39
+ type="tel"
40
+ @input="handlePhoneInput"
41
+ />
42
+ </v-col>
43
+ </v-row>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ const props = defineProps({
48
+ dialCode: {
49
+ type: String,
50
+ default: "+65",
51
+ },
52
+ phoneNumber: {
53
+ type: String,
54
+ default: "",
55
+ },
56
+ required: {
57
+ type: Boolean,
58
+ default: true,
59
+ },
60
+ });
61
+
62
+ const emit = defineEmits([
63
+ "update:dialCode",
64
+ "update:phoneNumber",
65
+ "update:isValid",
66
+ ]);
67
+
68
+ const { countries, getCountryByDialCode, formatPhoneNumber } =
69
+ usePhoneCountries();
70
+
71
+ const initialCountry = countries.find(
72
+ (country) => country.dialCode === props.dialCode
73
+ );
74
+ const localDialCode = ref(initialCountry?.label || props.dialCode);
75
+ const localPhoneNumber = ref(props.phoneNumber);
76
+
77
+ const countryOptions = computed(() => {
78
+ return countries.map((country) => ({
79
+ ...country,
80
+ display: `${country.country} (${country.dialCode})`,
81
+ }));
82
+ });
83
+
84
+ const selectedCountry = computed(() => {
85
+ const selected = countries.find(
86
+ (country) => country.label === localDialCode.value
87
+ );
88
+ return selected;
89
+ });
90
+
91
+ const dialCodeRequired = (v: string) => {
92
+ if (!props.required) return true;
93
+ return !!v || "Country code is required";
94
+ };
95
+
96
+ const phoneRequired = (v: string) => {
97
+ if (!props.required) return true;
98
+ return !!v || "Phone number is required";
99
+ };
100
+
101
+ const phoneDigitsOnly = (v: string) => {
102
+ if (!v) return props.required ? "Phone number is required" : true;
103
+ return (
104
+ /^\d+$/.test(v.replace(/\s|-|\(|\)/g, "")) || "Only numbers are allowed"
105
+ );
106
+ };
107
+
108
+ const phoneValidLength = (v: string) => {
109
+ if (!v || !selectedCountry.value) return true;
110
+
111
+ const cleanNumber = v.replace(/\s|-|\(|\)/g, "");
112
+ const { minLength, maxLength } = selectedCountry.value;
113
+
114
+ if (cleanNumber.length < minLength) {
115
+ return `Phone number must be at least ${minLength} digits`;
116
+ }
117
+ if (cleanNumber.length > maxLength) {
118
+ return `Phone number must not exceed ${maxLength} digits`;
119
+ }
120
+ return true;
121
+ };
122
+
123
+ const phoneCountryFormat = (v: string) => {
124
+ if (!v || !selectedCountry.value?.format) return true;
125
+
126
+ const cleanNumber = v.replace(/\s|-|\(|\)/g, "");
127
+ return (
128
+ selectedCountry.value.format.test(cleanNumber) ||
129
+ `Invalid ${selectedCountry.value.country} phone number format`
130
+ );
131
+ };
132
+
133
+ const phoneValidationRules = computed(() => [
134
+ phoneRequired,
135
+ phoneDigitsOnly,
136
+ phoneValidLength,
137
+ phoneCountryFormat,
138
+ ]);
139
+
140
+ const handlePhoneInput = (event: Event) => {
141
+ const target = event.target as HTMLInputElement;
142
+ const actualDialCode = selectedCountry.value?.dialCode || localDialCode.value;
143
+ const formatted = formatPhoneNumber(target.value, actualDialCode);
144
+ localPhoneNumber.value = formatted;
145
+ };
146
+
147
+ const isValid = computed(() => {
148
+ if (!props.required && !localPhoneNumber.value) return true;
149
+
150
+ return phoneValidationRules.value.every(
151
+ (rule) => rule(localPhoneNumber.value) === true
152
+ );
153
+ });
154
+
155
+ watch(localDialCode, (val) => {
156
+ const actualDialCode = selectedCountry.value?.dialCode || val;
157
+ emit("update:dialCode", actualDialCode);
158
+ });
159
+ watch(localPhoneNumber, (val) => emit("update:phoneNumber", val));
160
+ watch(isValid, (val) => emit("update:isValid", val));
161
+
162
+ watch(
163
+ () => props.dialCode,
164
+ (val) => {
165
+ // Find the country with matching dialCode and set the label
166
+ const matchingCountry = countries.find(
167
+ (country) => country.dialCode === val
168
+ );
169
+ localDialCode.value = matchingCountry?.label || val;
170
+ }
171
+ );
172
+ watch(
173
+ () => props.phoneNumber,
174
+ (val) => (localPhoneNumber.value = val)
175
+ );
176
+
177
+ watch(localDialCode, () => {
178
+ if (localPhoneNumber.value) {
179
+ const actualDialCode =
180
+ selectedCountry.value?.dialCode || localDialCode.value;
181
+ localPhoneNumber.value = formatPhoneNumber(
182
+ localPhoneNumber.value,
183
+ actualDialCode
184
+ );
185
+ }
186
+ });
187
+ </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-snackbar v-model="snackbar" :color="props.color">
2
+ <v-snackbar v-model="snackbar" :color="props.color" :z-index="100000000" >
3
3
  {{ props.text }}
4
4
 
5
5
  <template #actions>
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <!-- Top Actions -->
4
+ <v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
5
+ <v-row no-gutters>
6
+ <slot name="actions">
7
+ <v-btn
8
+ v-if="canCreate"
9
+ class="text-none"
10
+ rounded="pill"
11
+ variant="tonal"
12
+ size="large"
13
+ @click="emits('create')"
14
+ >
15
+ {{ createLabel }}
16
+ </v-btn>
17
+ </slot>
18
+ </v-row>
19
+ </v-col>
20
+
21
+ <!-- Table Card -->
22
+ <v-col cols="12">
23
+ <v-card
24
+ width="100%"
25
+ variant="outlined"
26
+ border="thin"
27
+ rounded="lg"
28
+ :loading="loading"
29
+ >
30
+ <!-- Toolbar -->
31
+ <v-toolbar density="compact" color="grey-lighten-4">
32
+ <template #prepend>
33
+ <v-btn fab icon density="comfortable" @click="emits('refresh')">
34
+ <v-icon>mdi-refresh</v-icon>
35
+ </v-btn>
36
+ </template>
37
+
38
+ <template #append>
39
+ <v-row no-gutters justify="end" align="center">
40
+ <span class="mr-2 text-caption text-fontgray">
41
+ {{ pageRange }}
42
+ </span>
43
+ <local-pagination
44
+ v-model="internalPage"
45
+ :length="pages"
46
+ @update:value="emits('update:page', internalPage)"
47
+ />
48
+ </v-row>
49
+ </template>
50
+ </v-toolbar>
51
+
52
+ <!-- Data Table -->
53
+ <v-data-table
54
+ :headers="headers"
55
+ :items="items"
56
+ :item-value="itemValue"
57
+ :items-per-page="itemsPerPage"
58
+ fixed-header
59
+ hide-default-footer
60
+ hide-default-header
61
+ @click:row="(_: any, data: any) => emits('row-click', data)"
62
+ style="max-height: calc(100vh - (200px))"
63
+ >
64
+ <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
65
+ <slot :name="slotName" v-bind="slotProps" />
66
+ </template>
67
+ </v-data-table>
68
+ </v-card>
69
+ </v-col>
70
+ </v-row>
71
+ </template>
72
+
73
+ <script lang="ts" setup>
74
+ const props = defineProps({
75
+ headers: {
76
+ type: Array as PropType<Array<Record<string, any>>>,
77
+ required: true,
78
+ },
79
+ items: {
80
+ type: Array as PropType<Array<Record<string, any>>>,
81
+ default: () => [],
82
+ },
83
+ loading: {
84
+ type: Boolean,
85
+ default: false,
86
+ },
87
+ canCreate: {
88
+ type: Boolean,
89
+ default: false,
90
+ },
91
+ createLabel: {
92
+ type: String,
93
+ default: "Add",
94
+ },
95
+ itemValue: {
96
+ type: String,
97
+ default: "_id",
98
+ },
99
+ itemsPerPage: {
100
+ type: Number,
101
+ default: 20,
102
+ },
103
+ page: {
104
+ type: Number,
105
+ default: 1,
106
+ },
107
+ pages: {
108
+ type: Number,
109
+ default: 0,
110
+ },
111
+ pageRange: {
112
+ type: String,
113
+ default: "-- - -- of --",
114
+ },
115
+ });
116
+
117
+ const emits = defineEmits(["create", "refresh", "update:page", "row-click"]);
118
+
119
+ const internalPage = ref(props.page);
120
+ watch(
121
+ () => props.page,
122
+ (val) => {
123
+ internalPage.value = val;
124
+ }
125
+ );
126
+ </script>
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <v-card flat border="sm black" :loading="loading" class="px-0" :ripple="false">
3
+ <v-toolbar class="px-5">
4
+ <template #prepend>
5
+ <span class="text-subtitle-1 font-weight-black">{{ tableTitle }}</span>
6
+ </template>
7
+ <template #append>
8
+ <v-btn color="primary" variant="flat" size="large" density="comfortable" :text="buttonLabel"
9
+ class="text-capitalize" />
10
+ </template>
11
+ </v-toolbar>
12
+ <v-card-text class="pa-0 bg-red">
13
+ <v-data-table :headers="headers" :items="items" :item-value="itemValue" :items-per-page="itemsPerPage"
14
+ fixed-header hide-default-footer hide-default-header @click:row="$emit('row-click', $event)"
15
+ style="max-height: calc(100vh - (200px))">
16
+ <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
17
+ <slot :name="slotName" v-bind="slotProps" />
18
+ </template>
19
+ </v-data-table>
20
+ </v-card-text>
21
+ </v-card>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ const selected = defineModel({
26
+ type: Array as PropType<Array<string>>,
27
+ default: () => [],
28
+ });
29
+ const page = defineModel("page", { type: Number, default: 0 });
30
+
31
+ const props = defineProps({
32
+ headers: {
33
+ type: Array as PropType<Array<Record<string, string | number>>>,
34
+ required: true,
35
+ default: () => [],
36
+ },
37
+ items: {
38
+ type: Array as PropType<Array<Record<string, any>>>,
39
+ required: true,
40
+ default: () => [],
41
+ },
42
+
43
+ itemValue: {
44
+ type: String,
45
+ default: "_id",
46
+ },
47
+ itemsPerPage: {
48
+ type: Number,
49
+ default: 20,
50
+ },
51
+ loading: {
52
+ type: Boolean,
53
+ required: true,
54
+ default: true,
55
+ },
56
+ tableTitle: {
57
+ type: String,
58
+ default: "",
59
+ required: false
60
+ },
61
+ buttonLabel: {
62
+ type: String,
63
+ default: "Add",
64
+ required: false
65
+ }
66
+ });
67
+
68
+ const emit = defineEmits(["refresh", "update:pagination", "row-click"]);
69
+
70
+ function increment() {
71
+ page.value++;
72
+ emit("update:pagination", page.value);
73
+ }
74
+
75
+ function decrement() {
76
+ page.value--;
77
+ emit("update:pagination", page.value);
78
+ }
79
+
80
+ function tableRowClickHandler(_: any, row: any) {
81
+ const item = props.items[row.index];
82
+ emit("row-click", item);
83
+ }
84
+
85
+ const selectAll = ref(false);
86
+
87
+ watch(selectAll, (curr) => {
88
+ selected.value.splice(0, selected.value.length);
89
+ if (curr) {
90
+ const ids = props.items.map((i) => i._id as string);
91
+ selected.value.push(...ids);
92
+ }
93
+ });
94
+ </script>
@@ -43,6 +43,8 @@ export default function useLocal() {
43
43
  { title: "Cleaning Services", value: "cleaning_services" },
44
44
  ];
45
45
 
46
+ const landingPage = useCookie("landing-page", cookieConfig);
47
+
46
48
  return {
47
49
  cookieConfig,
48
50
  getUserFromCookie,
@@ -50,5 +52,6 @@ export default function useLocal() {
50
52
  apps,
51
53
  headerSearch,
52
54
  natureOfBusiness,
55
+ landingPage,
53
56
  };
54
57
  }
@@ -3,11 +3,11 @@ export default function useLocalAuth() {
3
3
 
4
4
  const currentUser = useState<TUser | null>("currentUser", () => null);
5
5
 
6
- function authenticate() {
6
+ async function authenticate() {
7
7
  const user = useCookie("user", cookieConfig).value;
8
8
 
9
9
  const { data: getCurrentUserReq, error: getCurrentUserErr } =
10
- useLazyAsyncData("getCurrentUser", () =>
10
+ await useLazyAsyncData("getCurrentUser", () =>
11
11
  useNuxtApp().$api<TUser>(`/api/users/id/${user}`)
12
12
  );
13
13
 
@@ -44,10 +44,10 @@ export default function useLocalAuth() {
44
44
  }
45
45
 
46
46
  async function logout() {
47
- const refreshToken = useCookie("refreshToken", cookieConfig).value;
48
- if (refreshToken) {
47
+ const sid = useCookie("sid", cookieConfig).value;
48
+ if (sid) {
49
49
  try {
50
- await useNuxtApp().$api(`/api/auth/${refreshToken}`, {
50
+ await useNuxtApp().$api(`/api/auth/${sid}`, {
51
51
  method: "DELETE",
52
52
  });
53
53
 
@@ -0,0 +1,561 @@
1
+ export default function usePhoneCountries() {
2
+ const countries: TPhoneCountry[] = [
3
+ // Asia Pacific
4
+ {
5
+ _id: "sg",
6
+ country: "Singapore",
7
+ dialCode: "+65",
8
+ minLength: 8,
9
+ maxLength: 8,
10
+ placeholder: "8123 4567",
11
+ format: /^[89]\d{7}$/,
12
+ label: "+65",
13
+ },
14
+ {
15
+ _id: "my",
16
+ country: "Malaysia",
17
+ dialCode: "+60",
18
+ minLength: 9,
19
+ maxLength: 11,
20
+ placeholder: "12 345 6789",
21
+ format: /^1[0-9]\d{7,9}$/,
22
+ label: "+60",
23
+ },
24
+ {
25
+ _id: "ph",
26
+ country: "Philippines",
27
+ dialCode: "+63",
28
+ minLength: 10,
29
+ maxLength: 10,
30
+ placeholder: "917 123 4567",
31
+ format: /^9\d{9}$/,
32
+ label: "+63",
33
+ },
34
+ {
35
+ _id: "_id",
36
+ country: "Indonesia",
37
+ dialCode: "+62",
38
+ minLength: 9,
39
+ maxLength: 12,
40
+ placeholder: "812 3456 789",
41
+ format: /^8\d{8,11}$/,
42
+ label: "+62",
43
+ },
44
+ {
45
+ _id: "th",
46
+ country: "Thailand",
47
+ dialCode: "+66",
48
+ minLength: 9,
49
+ maxLength: 9,
50
+ placeholder: "81 234 5678",
51
+ format: /^[689]\d{8}$/,
52
+ label: "+66",
53
+ },
54
+ {
55
+ _id: "vn",
56
+ country: "Vietnam",
57
+ dialCode: "+84",
58
+ minLength: 9,
59
+ maxLength: 10,
60
+ placeholder: "912 345 678",
61
+ format: /^[3-9]\d{8,9}$/,
62
+ label: "+84",
63
+ },
64
+ {
65
+ _id: "kh",
66
+ country: "Cambodia",
67
+ dialCode: "+855",
68
+ minLength: 8,
69
+ maxLength: 9,
70
+ placeholder: "12 345 678",
71
+ format: /^[1-9]\d{7,8}$/,
72
+ label: "+855",
73
+ },
74
+ {
75
+ _id: "la",
76
+ country: "Laos",
77
+ dialCode: "+856",
78
+ minLength: 8,
79
+ maxLength: 10,
80
+ placeholder: "20 1234 567",
81
+ format: /^[2-9]\d{7,9}$/,
82
+ label: "+856",
83
+ },
84
+ {
85
+ _id: "mm",
86
+ country: "Myanmar",
87
+ dialCode: "+95",
88
+ minLength: 9,
89
+ maxLength: 10,
90
+ placeholder: "9 1234 5678",
91
+ format: /^9\d{8,9}$/,
92
+ label: "+95",
93
+ },
94
+ {
95
+ _id: "bn",
96
+ country: "Brunei",
97
+ dialCode: "+673",
98
+ minLength: 7,
99
+ maxLength: 7,
100
+ placeholder: "712 3456",
101
+ format: /^[2-8]\d{6}$/,
102
+ label: "+673",
103
+ },
104
+
105
+ // Oceania
106
+ {
107
+ _id: "au",
108
+ country: "Australia",
109
+ dialCode: "+61",
110
+ minLength: 9,
111
+ maxLength: 9,
112
+ placeholder: "412 345 678",
113
+ format: /^[2-9]\d{8}$/,
114
+ label: "+61",
115
+ },
116
+ {
117
+ _id: "nz",
118
+ country: "New Zealand",
119
+ dialCode: "+64",
120
+ minLength: 8,
121
+ maxLength: 9,
122
+ placeholder: "21 123 456",
123
+ format: /^[2-9]\d{7,8}$/,
124
+ label: "+64",
125
+ },
126
+ {
127
+ _id: "pg",
128
+ country: "Papua New Guinea",
129
+ dialCode: "+675",
130
+ minLength: 8,
131
+ maxLength: 8,
132
+ placeholder: "7012 3456",
133
+ format: /^[7-9]\d{7}$/,
134
+ label: "+675",
135
+ },
136
+ {
137
+ _id: "fj",
138
+ country: "Fiji",
139
+ dialCode: "+679",
140
+ minLength: 7,
141
+ maxLength: 7,
142
+ placeholder: "712 3456",
143
+ format: /^[7-9]\d{6}$/,
144
+ label: "+679",
145
+ },
146
+
147
+ // North America
148
+ {
149
+ _id: "us",
150
+ country: "United States",
151
+ dialCode: "+1",
152
+ minLength: 10,
153
+ maxLength: 10,
154
+ placeholder: "(555) 123-4567",
155
+ format: /^\d{10}$/,
156
+ label: "+1 (US)",
157
+ },
158
+ {
159
+ _id: "ca",
160
+ country: "Canada",
161
+ dialCode: "+1",
162
+ minLength: 10,
163
+ maxLength: 10,
164
+ placeholder: "(416) 123-4567",
165
+ format: /^\d{10}$/,
166
+ label: "+1 (CA)",
167
+ },
168
+
169
+ // Europe
170
+ {
171
+ _id: "gb",
172
+ country: "United Kingdom",
173
+ dialCode: "+44",
174
+ minLength: 10,
175
+ maxLength: 11,
176
+ placeholder: "7700 900123",
177
+ format: /^[1-9]\d{9,10}$/,
178
+ label: "+44",
179
+ },
180
+ {
181
+ _id: "de",
182
+ country: "Germany",
183
+ dialCode: "+49",
184
+ minLength: 10,
185
+ maxLength: 12,
186
+ placeholder: "151 12345678",
187
+ format: /^[1-9]\d{9,11}$/,
188
+ label: "+49",
189
+ },
190
+ {
191
+ _id: "fr",
192
+ country: "France",
193
+ dialCode: "+33",
194
+ minLength: 9,
195
+ maxLength: 9,
196
+ placeholder: "6 12 34 56 78",
197
+ format: /^[1-9]\d{8}$/,
198
+ label: "+33",
199
+ },
200
+ {
201
+ _id: "it",
202
+ country: "Italy",
203
+ dialCode: "+39",
204
+ minLength: 9,
205
+ maxLength: 11,
206
+ placeholder: "312 345 6789",
207
+ format: /^3\d{8,10}$/,
208
+ label: "+39",
209
+ },
210
+ {
211
+ _id: "es",
212
+ country: "Spain",
213
+ dialCode: "+34",
214
+ minLength: 9,
215
+ maxLength: 9,
216
+ placeholder: "612 34 56 78",
217
+ format: /^[6-9]\d{8}$/,
218
+ label: "+34",
219
+ },
220
+ {
221
+ _id: "nl",
222
+ country: "Netherlands",
223
+ dialCode: "+31",
224
+ minLength: 9,
225
+ maxLength: 9,
226
+ placeholder: "6 12345678",
227
+ format: /^6\d{8}$/,
228
+ label: "+31",
229
+ },
230
+ {
231
+ _id: "ch",
232
+ country: "Switzerland",
233
+ dialCode: "+41",
234
+ minLength: 9,
235
+ maxLength: 9,
236
+ placeholder: "78 123 45 67",
237
+ format: /^[7-9]\d{8}$/,
238
+ label: "+41",
239
+ },
240
+ {
241
+ _id: "at",
242
+ country: "Austria",
243
+ dialCode: "+43",
244
+ minLength: 10,
245
+ maxLength: 11,
246
+ placeholder: "664 123456",
247
+ format: /^[6-9]\d{9,10}$/,
248
+ label: "+43",
249
+ },
250
+ {
251
+ _id: "be",
252
+ country: "Belgium",
253
+ dialCode: "+32",
254
+ minLength: 9,
255
+ maxLength: 9,
256
+ placeholder: "470 12 34 56",
257
+ format: /^4\d{8}$/,
258
+ label: "+32",
259
+ },
260
+ {
261
+ _id: "se",
262
+ country: "Sweden",
263
+ dialCode: "+46",
264
+ minLength: 9,
265
+ maxLength: 9,
266
+ placeholder: "70 123 45 67",
267
+ format: /^7\d{8}$/,
268
+ label: "+46",
269
+ },
270
+ {
271
+ _id: "no",
272
+ country: "Norway",
273
+ dialCode: "+47",
274
+ minLength: 8,
275
+ maxLength: 8,
276
+ placeholder: "412 34 567",
277
+ format: /^[4-9]\d{7}$/,
278
+ label: "+47",
279
+ },
280
+ {
281
+ _id: "dk",
282
+ country: "Denmark",
283
+ dialCode: "+45",
284
+ minLength: 8,
285
+ maxLength: 8,
286
+ placeholder: "20 12 34 56",
287
+ format: /^[2-9]\d{7}$/,
288
+ label: "+45",
289
+ },
290
+ {
291
+ _id: "fi",
292
+ country: "Finland",
293
+ dialCode: "+358",
294
+ minLength: 9,
295
+ maxLength: 10,
296
+ placeholder: "50 123 4567",
297
+ format: /^[4-5]\d{8,9}$/,
298
+ label: "+358",
299
+ },
300
+
301
+ // Middle East
302
+ {
303
+ _id: "sa",
304
+ country: "Saudi Arabia",
305
+ dialCode: "+966",
306
+ minLength: 9,
307
+ maxLength: 9,
308
+ placeholder: "50 123 4567",
309
+ format: /^5\d{8}$/,
310
+ label: "+966",
311
+ },
312
+ {
313
+ _id: "ae",
314
+ country: "United Arab Emirates",
315
+ dialCode: "+971",
316
+ minLength: 9,
317
+ maxLength: 9,
318
+ placeholder: "50 123 4567",
319
+ format: /^5\d{8}$/,
320
+ label: "+971",
321
+ },
322
+ {
323
+ _id: "qa",
324
+ country: "Qatar",
325
+ dialCode: "+974",
326
+ minLength: 8,
327
+ maxLength: 8,
328
+ placeholder: "3012 3456",
329
+ format: /^[3-7]\d{7}$/,
330
+ label: "+974",
331
+ },
332
+ {
333
+ _id: "kw",
334
+ country: "Kuwait",
335
+ dialCode: "+965",
336
+ minLength: 8,
337
+ maxLength: 8,
338
+ placeholder: "5012 3456",
339
+ format: /^[569]\d{7}$/,
340
+ label: "+965",
341
+ },
342
+ {
343
+ _id: "bh",
344
+ country: "Bahrain",
345
+ dialCode: "+973",
346
+ minLength: 8,
347
+ maxLength: 8,
348
+ placeholder: "3612 3456",
349
+ format: /^[3-9]\d{7}$/,
350
+ label: "+973",
351
+ },
352
+ {
353
+ _id: "om",
354
+ country: "Oman",
355
+ dialCode: "+968",
356
+ minLength: 8,
357
+ maxLength: 8,
358
+ placeholder: "9012 3456",
359
+ format: /^[79]\d{7}$/,
360
+ label: "+968",
361
+ },
362
+
363
+ // East Asia
364
+ {
365
+ _id: "cn",
366
+ country: "China",
367
+ dialCode: "+86",
368
+ minLength: 11,
369
+ maxLength: 11,
370
+ placeholder: "138 0013 8000",
371
+ format: /^1[3-9]\d{9}$/,
372
+ label: "+86",
373
+ },
374
+ {
375
+ _id: "jp",
376
+ country: "Japan",
377
+ dialCode: "+81",
378
+ minLength: 10,
379
+ maxLength: 11,
380
+ placeholder: "90 1234 5678",
381
+ format: /^[7-9]\d{9,10}$/,
382
+ label: "+81",
383
+ },
384
+ {
385
+ _id: "kr",
386
+ country: "South Korea",
387
+ dialCode: "+82",
388
+ minLength: 10,
389
+ maxLength: 11,
390
+ placeholder: "10 1234 5678",
391
+ format: /^1[0-9]\d{8,9}$/,
392
+ label: "+82",
393
+ },
394
+ {
395
+ _id: "hk",
396
+ country: "Hong Kong",
397
+ dialCode: "+852",
398
+ minLength: 8,
399
+ maxLength: 8,
400
+ placeholder: "5123 4567",
401
+ format: /^[5-9]\d{7}$/,
402
+ label: "+852",
403
+ },
404
+ {
405
+ _id: "tw",
406
+ country: "Taiwan",
407
+ dialCode: "+886",
408
+ minLength: 9,
409
+ maxLength: 9,
410
+ placeholder: "912 345 678",
411
+ format: /^9\d{8}$/,
412
+ label: "+886",
413
+ },
414
+ {
415
+ _id: "mo",
416
+ country: "Macau",
417
+ dialCode: "+853",
418
+ minLength: 8,
419
+ maxLength: 8,
420
+ placeholder: "6123 4567",
421
+ format: /^6\d{7}$/,
422
+ label: "+853",
423
+ },
424
+
425
+ // South Asia
426
+ {
427
+ _id: "in",
428
+ country: "India",
429
+ dialCode: "+91",
430
+ minLength: 10,
431
+ maxLength: 10,
432
+ placeholder: "98765 43210",
433
+ format: /^[6-9]\d{9}$/,
434
+ label: "+91",
435
+ },
436
+ {
437
+ _id: "pk",
438
+ country: "Pakistan",
439
+ dialCode: "+92",
440
+ minLength: 10,
441
+ maxLength: 10,
442
+ placeholder: "301 2345678",
443
+ format: /^3\d{9}$/,
444
+ label: "+92",
445
+ },
446
+ {
447
+ _id: "bd",
448
+ country: "Bangladesh",
449
+ dialCode: "+880",
450
+ minLength: 10,
451
+ maxLength: 10,
452
+ placeholder: "1712 345678",
453
+ format: /^1[3-9]\d{8}$/,
454
+ label: "+880",
455
+ },
456
+ {
457
+ _id: "lk",
458
+ country: "Sri Lanka",
459
+ dialCode: "+94",
460
+ minLength: 9,
461
+ maxLength: 9,
462
+ placeholder: "71 234 5678",
463
+ format: /^7[0-8]\d{7}$/,
464
+ label: "+94",
465
+ },
466
+ {
467
+ _id: "np",
468
+ country: "Nepal",
469
+ dialCode: "+977",
470
+ minLength: 10,
471
+ maxLength: 10,
472
+ placeholder: "98 1234 5678",
473
+ format: /^9[78]\d{8}$/,
474
+ label: "+977",
475
+ },
476
+ {
477
+ _id: "mv",
478
+ country: "Maldives",
479
+ dialCode: "+960",
480
+ minLength: 7,
481
+ maxLength: 7,
482
+ placeholder: "791 2345",
483
+ format: /^[79]\d{6}$/,
484
+ label: "+960",
485
+ },
486
+ ];
487
+
488
+ const getCountryByDialCode = (
489
+ dialCode: string
490
+ ): TPhoneCountry | undefined => {
491
+ return countries.find((country) => country.dialCode === dialCode);
492
+ };
493
+
494
+ const getDialCodes = (): string[] => {
495
+ return countries.map((country) => country.dialCode);
496
+ };
497
+
498
+ const formatPhoneNumber = (phoneNumber: string, dialCode: string): string => {
499
+ const country = getCountryByDialCode(dialCode);
500
+ if (!country) return phoneNumber;
501
+
502
+ let value = phoneNumber.replace(/\D/g, ""); // Remove non-digits
503
+
504
+ switch (dialCode) {
505
+ case "+65": // Singapore
506
+ if (value.length > 4) {
507
+ value = value.substring(0, 4) + " " + value.substring(4, 8);
508
+ }
509
+ break;
510
+ case "+60": // Malaysia
511
+ if (value.length > 2) {
512
+ value =
513
+ value.substring(0, 2) +
514
+ " " +
515
+ value.substring(2, 5) +
516
+ " " +
517
+ value.substring(5);
518
+ }
519
+ break;
520
+ case "+1": // US/Canada
521
+ if (value.length > 6) {
522
+ value =
523
+ "(" +
524
+ value.substring(0, 3) +
525
+ ") " +
526
+ value.substring(3, 6) +
527
+ "-" +
528
+ value.substring(6, 10);
529
+ } else if (value.length > 3) {
530
+ value = "(" + value.substring(0, 3) + ") " + value.substring(3);
531
+ }
532
+ break;
533
+ case "+44": // UK
534
+ if (value.length > 4) {
535
+ value = value.substring(0, 4) + " " + value.substring(4);
536
+ }
537
+ break;
538
+ default:
539
+ // Generic formatting for other countries
540
+ if (value.length > 3 && value.length <= 6) {
541
+ value = value.substring(0, 3) + " " + value.substring(3);
542
+ } else if (value.length > 6) {
543
+ value =
544
+ value.substring(0, 3) +
545
+ " " +
546
+ value.substring(3, 6) +
547
+ " " +
548
+ value.substring(6);
549
+ }
550
+ }
551
+
552
+ return value;
553
+ };
554
+
555
+ return {
556
+ countries,
557
+ getCountryByDialCode,
558
+ getDialCodes,
559
+ formatPhoneNumber,
560
+ };
561
+ }
@@ -301,6 +301,51 @@ export default function useUtils() {
301
301
  return n + (s[(v - 20) % 10] || s[v] || s[0]);
302
302
  }
303
303
 
304
+ function formatDateISO8601(date: Date): string {
305
+ const pad = (n: number) => n.toString().padStart(2, '0');
306
+ const year = date.getFullYear();
307
+ const month = pad(date.getMonth() + 1);
308
+ const day = pad(date.getDate());
309
+ const hours = pad(date.getHours());
310
+ const minutes = pad(date.getMinutes());
311
+ return `${year}-${month}-${day}T${hours}:${minutes}`;
312
+ }
313
+
314
+ function isValidBaseURL(baseURL: string | null) {
315
+ if (!baseURL) return true;
316
+
317
+ // Check if the baseURL starts with http:// or https://
318
+ if (!baseURL.startsWith("http://") && !baseURL.startsWith("https://")) {
319
+ return 'Base URL should start with "https://" or "http://"';
320
+ }
321
+
322
+ try {
323
+ const url = new URL(baseURL);
324
+
325
+ // Reject if there's any path beyond root
326
+ if (url.pathname !== "/" && url.pathname !== "") {
327
+ return "Invalid Base URL";
328
+ }
329
+
330
+ // Also reject if there’s a query or hash
331
+ if (url.search || url.hash) {
332
+ return "Invalid Base URL";
333
+ }
334
+
335
+ return ["http:", "https:"].includes(url.protocol)
336
+ ? true
337
+ : "Invalid Base URL";
338
+ } catch (e: any) {
339
+ return "Invalid Base URL";
340
+ }
341
+ }
342
+
343
+ function formatCamelCaseToWords(key: string){
344
+ return key
345
+ .replace(/([A-Z])/g, ' $1')
346
+ .replace(/^./, str => str.toUpperCase());
347
+ }
348
+
304
349
  return {
305
350
  requiredRule,
306
351
  emailRule,
@@ -331,5 +376,8 @@ export default function useUtils() {
331
376
  replaceMatch,
332
377
  setRouteParams,
333
378
  toOrdinal,
379
+ formatDateISO8601,
380
+ isValidBaseURL,
381
+ formatCamelCaseToWords
334
382
  };
335
383
  }
package/nuxt.config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
2
  import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
3
3
  export default defineNuxtConfig({
4
+ ssr: false,
4
5
  devtools: { enabled: true },
5
6
  build: {
6
7
  transpile: ["vuetify", "nuxt-signature-pad"],
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@iservice365/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.0.0",
5
+ "version": "1.0.1",
6
6
  "main": "./nuxt.config.ts",
7
7
  "scripts": {
8
8
  "dev": "nuxi dev .playground",
@@ -0,0 +1,10 @@
1
+ declare type TPhoneCountry = {
2
+ _id: string;
3
+ country: string;
4
+ dialCode: string;
5
+ minLength: number;
6
+ maxLength: number;
7
+ placeholder: string;
8
+ format?: RegExp;
9
+ label: string;
10
+ };
package/types/site.d.ts CHANGED
@@ -9,6 +9,8 @@ declare type TSite = {
9
9
  createdAt?: string;
10
10
  updatedAt?: string;
11
11
  deletedAt?: string;
12
- };
12
+ metadata?: {
13
+ block?: number; // Number of blocks in the site
14
+ }} ;
13
15
 
14
16
  declare type TSiteCreate = Pick<TSite, "name" | "description" | "orgId">;