|
<script setup> |
|
import {Link, useForm} from '@inertiajs/vue3'; |
|
import InputError from '@/Components/InputError.vue'; |
|
import InputLabel from '@/Components/InputLabel.vue'; |
|
import PrimaryButton from '@/Components/PrimaryButton.vue'; |
|
import TextInput from '@/Components/TextInput.vue'; |
|
import AppHead from "@/Layouts/AppHead.vue"; |
|
import {reactive, ref, toRefs} from "vue"; |
|
import axios from "axios"; |
|
import debounce from "lodash/debounce"; |
|
import CheckBadgeIcon from "@heroicons/vue/24/solid/CheckBadgeIcon.js"; |
|
import XCircleIcon from "@heroicons/vue/24/solid/XCircleIcon.js"; |
|
|
|
const form = useForm({ |
|
email: '', |
|
username: '', |
|
password: '', |
|
password_confirmation: '', |
|
terms: false, |
|
}); |
|
|
|
const state = reactive({ |
|
available: false, |
|
unavailable: false |
|
}); |
|
|
|
let message = ref(''); |
|
|
|
// create a function that lists all the usernames that are not allowed |
|
const unavailableUsernames = ['admin', 'root', 'administrator']; |
|
|
|
const checkUsername = () => { |
|
if (form.username.length < 4) { |
|
state.available = false; |
|
state.unavailable = false; |
|
message = ''; |
|
form.errors.username = null; |
|
return; |
|
} else if (form.username.length > 20) { |
|
state.available = false; |
|
state.unavailable = true; |
|
message = ''; |
|
form.errors.username = 'Username must be less than 20 characters'; |
|
return; |
|
} else if (unavailableUsernames.includes(form.username)) { |
|
state.available = false; |
|
state.unavailable = true; |
|
form.errors.username = 'Username is forbidden'; |
|
return; |
|
} else { |
|
axios.get(route('users.exists', form.username)) |
|
.then(response => { |
|
if (response.data.exists) { |
|
state.available = false; |
|
state.unavailable = true; |
|
message = ''; |
|
form.errors.username = 'The username has already been taken.'; |
|
} else if (!response.data.exists) { |
|
state.available = true; |
|
state.unavailable = false; |
|
message = 'Username is available'; |
|
form.errors.username = null; |
|
} |
|
}).catch(error => { |
|
console.error(error); |
|
}); |
|
} |
|
}; |
|
|
|
// set debounce delay to 500ms |
|
const checkUsernameDebounced = debounce(checkUsername, 500); |
|
|
|
const handleUsernameInput = () => { |
|
// update form state |
|
form.username = event.target.value; |
|
// debounce the checkUsername function |
|
checkUsernameDebounced(); |
|
}; |
|
|
|
|
|
const {available, unavailable} = toRefs(state); |
|
|
|
const submit = () => { |
|
form.post(route('register'), { |
|
onFinish: () => form.reset('password', 'password_confirmation'), |
|
}); |
|
}; |
|
</script> |
|
|
|
<template> |
|
<AppHead title="Register"> |
|
<meta content="Register" name="description"/> |
|
</AppHead> |
|
|
|
<form @submit.prevent="submit"> |
|
<div class="m-2"> |
|
<InputLabel for="username" value="Username"/> |
|
<div class="flex items-center justify-end"> |
|
<TextInput |
|
id="username" |
|
v-model="form.username" |
|
autofocus |
|
class="mt-1 block w-full" |
|
type="text" |
|
@input="handleUsernameInput" |
|
/> |
|
<div class="absolute"> |
|
<CheckBadgeIcon v-show="available" class="z-50 mt-1 mr-2 text-green-500 h-[28px] w-[28px]"/> |
|
<XCircleIcon v-show="unavailable" class="z-50 mt-1 mr-2 text-red-500 h-[28px] w-[28px]"/> |
|
</div> |
|
</div> |
|
<div v-show="message" class="mt-2 text-sm text-green-600">{{ message }}</div> |
|
<InputError :message="form.errors.username" class="mt-2"/> |
|
</div> |
|
|
|
<div class="m-2 mt-4"> |
|
<InputLabel for="email" value="Email"/> |
|
<TextInput |
|
id="email" |
|
v-model="form.email" |
|
autocomplete="email" |
|
class="mt-1 block w-full" |
|
type="email" |
|
/> |
|
<InputError :message="form.errors.email" class="mt-2"/> |
|
</div> |
|
|
|
<div class="m-2 mt-4"> |
|
<InputLabel for="password" value="Password"/> |
|
<TextInput |
|
id="password" |
|
v-model="form.password" |
|
autocomplete="new-password" |
|
class="mt-1 block w-full" |
|
type="password" |
|
/> |
|
<InputError :message="form.errors.password" class="mt-2"/> |
|
</div> |
|
|
|
<div class="m-2 mt-4"> |
|
<InputLabel for="password_confirmation" value="Confirm Password"/> |
|
<TextInput |
|
id="password_confirmation" |
|
v-model="form.password_confirmation" |
|
autocomplete="new-password" |
|
class="mt-1 block w-full" |
|
type="password" |
|
/> |
|
<InputError :message="form.errors.password_confirmation" class="mt-2"/> |
|
</div> |
|
|
|
<div class="mt-4 flex items-center justify-end"> |
|
<Link :href="route('login')" class="text-sm text-gray-600 underline hover:text-gray-900"> |
|
Already registered? |
|
</Link> |
|
|
|
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing" class="ml-4"> |
|
Register |
|
</PrimaryButton> |
|
</div> |
|
<div class="m-2 mt-4 border-t border-gray-500 pt-4"> |
|
<p class="text-center text-xs text-gray-500"> |
|
Social Login Coming Soon here... |
|
</p> |
|
</div> |
|
</form> |
|
</template> |
|
|
|
<style> |
|
|
|
.right-inner-addon { |
|
position: relative; |
|
} |
|
|
|
.right-inner-addon input { |
|
padding-right: 30px; |
|
width: 170px; /* 200px - 30px */ |
|
} |
|
|
|
.right-inner-addon svg { |
|
position: absolute; |
|
right: -10px; |
|
padding: 10px 12px; |
|
pointer-events: none; |
|
|
|
top: -8px; |
|
} |
|
</style> |
Nice!