@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 +6 -0
- package/components/Input/DateTimePicker.vue +71 -0
- package/components/Input/PhoneNumber.vue +187 -0
- package/components/Snackbar.vue +1 -1
- package/components/TableMain.vue +126 -0
- package/components/TableWithButton.vue +94 -0
- package/composables/useLocal.ts +3 -0
- package/composables/useLocalAuth.ts +5 -5
- package/composables/usePhoneCountries.ts +561 -0
- package/composables/useUtils.ts +48 -0
- package/nuxt.config.ts +1 -0
- package/package.json +1 -1
- package/types/phone-number.d.ts +10 -0
- package/types/site.d.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -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>
|
package/components/Snackbar.vue
CHANGED
|
@@ -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>
|
package/composables/useLocal.ts
CHANGED
|
@@ -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
|
|
48
|
-
if (
|
|
47
|
+
const sid = useCookie("sid", cookieConfig).value;
|
|
48
|
+
if (sid) {
|
|
49
49
|
try {
|
|
50
|
-
await useNuxtApp().$api(`/api/auth/${
|
|
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
|
+
}
|
package/composables/useUtils.ts
CHANGED
|
@@ -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
package/package.json
CHANGED
package/types/site.d.ts
CHANGED