Improve the name-editor
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
VITE_API_URL=https://localhost:5001/
|
VITE_API_URL=https://localhost:5001/
|
||||||
|
VITE_APP_BASE_URL=http://localhost:5173
|
||||||
|
VITE_APP_API_URL=http://localhost:5173/api
|
||||||
VITE_STRIPE_API_KEY=pk_test_51OoveVDrRyqXtNdB2st1NgA8WQA9rhgGaf3q7bCpAOoQyyRS30HMCzGeHba7meVGCSPfb1BVWmOTmFOcr9MkKf5H00bLu5MqsS
|
VITE_STRIPE_API_KEY=pk_test_51OoveVDrRyqXtNdB2st1NgA8WQA9rhgGaf3q7bCpAOoQyyRS30HMCzGeHba7meVGCSPfb1BVWmOTmFOcr9MkKf5H00bLu5MqsS
|
||||||
VITE_GOOGLE_CLIENT_ID=213344094492-9dbaet2gaschju3hj1sgv1umk0qpd833.apps.googleusercontent.com
|
VITE_GOOGLE_CLIENT_ID=213344094492-9dbaet2gaschju3hj1sgv1umk0qpd833.apps.googleusercontent.com
|
||||||
VITE_FACEBOOK_APP_ID
|
VITE_FACEBOOK_APP_ID
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
VITE_API_URL=https://hutopy-backend-api.azurewebsites.net
|
VITE_API_URL=https://hutopy-backend-api.azurewebsites.net
|
||||||
|
VITE_APP_BASE_URL=https://hutopy.ca
|
||||||
|
VITE_APP_API_URL=https://hutopy.ca/api
|
||||||
VITE_STRIPE_API_KEY=51OoveVDrRyqXtNdBAxIo183PujtqFyU0xUMK9YNtIijcHeDlcLN6pqkZWHbgaBA0FHrwLMSoy3yVLN33NX8ExOxL00MSZwgJN7
|
VITE_STRIPE_API_KEY=51OoveVDrRyqXtNdBAxIo183PujtqFyU0xUMK9YNtIijcHeDlcLN6pqkZWHbgaBA0FHrwLMSoy3yVLN33NX8ExOxL00MSZwgJN7
|
||||||
VITE_GOOGLE_CLIENT_ID=213344094492-7c83lqoh7mnjgadpeqo2lcs1krhbsnnd.apps.googleusercontent.com
|
VITE_GOOGLE_CLIENT_ID=213344094492-7c83lqoh7mnjgadpeqo2lcs1krhbsnnd.apps.googleusercontent.com
|
||||||
AZURE_SUBSCRIPTION_ID=46feb20f-3ae1-495a-830b-a31f7b76483d
|
AZURE_SUBSCRIPTION_ID=46feb20f-3ae1-495a-830b-a31f7b76483d
|
||||||
|
|||||||
7
frontend/src/config.js
Normal file
7
frontend/src/config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Environment-specific configuration
|
||||||
|
const config = {
|
||||||
|
baseUrl: import.meta.env.VITE_APP_BASE_URL || 'https://hutopy.ca',
|
||||||
|
apiUrl: import.meta.env.VITE_APP_API_URL || 'https://hutopy.ca/api',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
import {ref, onMounted, onUnmounted, computed} from "vue";
|
import {ref, onMounted, onUnmounted, computed} from "vue";
|
||||||
import {v7} from "uuid";
|
import {v7} from "uuid";
|
||||||
import {useClient} from "@/plugins/api.js";
|
import {useClient} from "@/plugins/api.js";
|
||||||
import { useI18n } from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: {
|
name: {
|
||||||
@@ -23,10 +24,11 @@ const emits = defineEmits([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const name = ref(props.name);
|
const name = ref(props.name);
|
||||||
const { t } = useI18n();
|
const {t} = useI18n();
|
||||||
|
|
||||||
const isOperationPending = ref(false);
|
const isOperationPending = ref(false);
|
||||||
const reservationState = ref(null);
|
const reservationState = ref(null);
|
||||||
|
const validationError = ref('');
|
||||||
|
|
||||||
// Use the reservationId from props if provided, otherwise generate a new one
|
// Use the reservationId from props if provided, otherwise generate a new one
|
||||||
const reservationId = ref(props.creatorNameReservationId || v7());
|
const reservationId = ref(props.creatorNameReservationId || v7());
|
||||||
@@ -36,6 +38,27 @@ const isCurrentSlug = computed(() => {
|
|||||||
return props.originalSlug && name.value === props.originalSlug;
|
return props.originalSlug && name.value === props.originalSlug;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Base URL for display
|
||||||
|
const baseUrl = computed(() => `${config.baseUrl}/@`);
|
||||||
|
|
||||||
|
// Validation function for the slug
|
||||||
|
const validateSlug = (slug) => {
|
||||||
|
if (!slug) {
|
||||||
|
validationError.value = t('creator.name.errors.required');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow letters, numbers, and hyphens
|
||||||
|
const validSlugRegex = /^[a-zA-Z0-9-]+$/;
|
||||||
|
if (!validSlugRegex.test(slug)) {
|
||||||
|
validationError.value = t('creator.name.errors.invalid');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError.value = '';
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure we emit the reservationId on mount if we generated a new one
|
// Ensure we emit the reservationId on mount if we generated a new one
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!props.creatorNameReservationId) {
|
if (!props.creatorNameReservationId) {
|
||||||
@@ -68,6 +91,12 @@ const handleInput = () => {
|
|||||||
return; // Skip if we've already processed this exact name
|
return; // Skip if we've already processed this exact name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the slug
|
||||||
|
if (!validateSlug(currentName)) {
|
||||||
|
reservationState.value = "unavailable";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the name is the same as the original slug, set reservation state to "reserved"
|
// If the name is the same as the original slug, set reservation state to "reserved"
|
||||||
if (props.originalSlug && currentName === props.originalSlug) {
|
if (props.originalSlug && currentName === props.originalSlug) {
|
||||||
reservationState.value = "reserved";
|
reservationState.value = "reserved";
|
||||||
@@ -100,9 +129,9 @@ const checkNameAvailability = async (nameToCheck) => {
|
|||||||
currentController = controller;
|
currentController = controller;
|
||||||
|
|
||||||
await client.post(
|
await client.post(
|
||||||
`/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`,
|
`/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`,
|
||||||
{ reservationId: reservationId.value },
|
{reservationId: reservationId.value},
|
||||||
{ signal: controller.signal }
|
{signal: controller.signal}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only process the response if this is still the current request
|
// Only process the response if this is still the current request
|
||||||
@@ -137,9 +166,13 @@ onUnmounted(() => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
:label="t('creator.name.label')"
|
:label="t('creator.name.label')"
|
||||||
v-model="name"
|
v-model="name"
|
||||||
outlined
|
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
|
:error-messages="validationError"
|
||||||
>
|
>
|
||||||
|
<template #prepend-inner>
|
||||||
|
<span class="text-gray-400 font-sans">{{ baseUrl }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #append-inner>
|
<template #append-inner>
|
||||||
<v-progress-circular
|
<v-progress-circular
|
||||||
v-if="reservationState === 'loading'"
|
v-if="reservationState === 'loading'"
|
||||||
@@ -164,21 +197,33 @@ onUnmounted(() => {
|
|||||||
"en": {
|
"en": {
|
||||||
"creator": {
|
"creator": {
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Your creator handle"
|
"label": "Your creator handle",
|
||||||
|
"errors": {
|
||||||
|
"required": "Creator handle is required",
|
||||||
|
"invalid": "Only letters, numbers, and hyphens are allowed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
"creator": {
|
"creator": {
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Votre identifiant de créateur"
|
"label": "Votre identifiant de créateur",
|
||||||
|
"errors": {
|
||||||
|
"required": "L'identifiant est requis",
|
||||||
|
"invalid": "Seules les lettres, chiffres et tirets sont autorisés"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"es": {
|
"es": {
|
||||||
"creator": {
|
"creator": {
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Tu identificador de creador"
|
"label": "Tu identificador de creador",
|
||||||
|
"errors": {
|
||||||
|
"required": "El identificador es obligatorio",
|
||||||
|
"invalid": "Solo se permiten letras, números y guiones"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user