diff --git a/frontend/.env.development b/frontend/.env.development index eea1831..bb4d8a1 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,4 +1,6 @@ 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_GOOGLE_CLIENT_ID=213344094492-9dbaet2gaschju3hj1sgv1umk0qpd833.apps.googleusercontent.com VITE_FACEBOOK_APP_ID \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production index 3eabb32..888b727 100644 --- a/frontend/.env.production +++ b/frontend/.env.production @@ -1,4 +1,6 @@ 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_GOOGLE_CLIENT_ID=213344094492-7c83lqoh7mnjgadpeqo2lcs1krhbsnnd.apps.googleusercontent.com AZURE_SUBSCRIPTION_ID=46feb20f-3ae1-495a-830b-a31f7b76483d diff --git a/frontend/src/config.js b/frontend/src/config.js new file mode 100644 index 0000000..934032b --- /dev/null +++ b/frontend/src/config.js @@ -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; \ No newline at end of file diff --git a/frontend/src/views/creators/NameEditor.vue b/frontend/src/views/creators/NameEditor.vue index 74d2d25..95bb84b 100644 --- a/frontend/src/views/creators/NameEditor.vue +++ b/frontend/src/views/creators/NameEditor.vue @@ -2,7 +2,8 @@ import {ref, onMounted, onUnmounted, computed} from "vue"; import {v7} from "uuid"; import {useClient} from "@/plugins/api.js"; -import { useI18n } from 'vue-i18n'; +import {useI18n} from 'vue-i18n'; +import config from '@/config'; const props = defineProps({ name: { @@ -23,10 +24,11 @@ const emits = defineEmits([ ]); const name = ref(props.name); -const { t } = useI18n(); +const {t} = useI18n(); const isOperationPending = ref(false); const reservationState = ref(null); +const validationError = ref(''); // Use the reservationId from props if provided, otherwise generate a new one const reservationId = ref(props.creatorNameReservationId || v7()); @@ -36,12 +38,33 @@ const isCurrentSlug = computed(() => { 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 onMounted(() => { if (!props.creatorNameReservationId) { emits('update:creatorNameReservationId', reservationId.value); } - + // If the name is the same as the original slug, set reservation state to "reserved" if (isCurrentSlug.value) { reservationState.value = "reserved"; @@ -67,7 +90,13 @@ const handleInput = () => { if (currentName === lastProcessedName) { 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 (props.originalSlug && currentName === props.originalSlug) { reservationState.value = "reserved"; @@ -75,7 +104,7 @@ const handleInput = () => { emits('update:name', currentName); return; } - + checkNameAvailability(currentName); }, 200); }; @@ -100,9 +129,9 @@ const checkNameAvailability = async (nameToCheck) => { currentController = controller; await client.post( - `/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`, - { reservationId: reservationId.value }, - { signal: controller.signal } + `/api/creators/@${encodeURIComponent(nameToCheck)}/reserve`, + {reservationId: reservationId.value}, + {signal: controller.signal} ); // Only process the response if this is still the current request @@ -137,9 +166,13 @@ onUnmounted(() => { variant="outlined" :label="t('creator.name.label')" v-model="name" - outlined @input="handleInput" + :error-messages="validationError" > + +