Rename resources/scripts-v2 to resources/scripts and drop @v2 alias

Now that the legacy v1 frontend (commit 064bdf53) is gone, the v2 directory is the only frontend and the v2 suffix is just noise. Renames resources/scripts-v2 to resources/scripts via git mv (so git records the move as renames, preserving blame and log --follow), then bulk-rewrites the 152 files that imported via @v2/... to use @/scripts/... instead. The existing @ alias (resources/) covers the new path with no extra config needed.

Drops the now-unused @v2 alias from vite.config.js and points the laravel-vite-plugin entry at resources/scripts/main.ts. Updates the only blade reference (resources/views/app.blade.php) to match. The package.json test script (eslint ./resources/scripts) automatically targets the right place after the rename without any edit.

Verified: npm run build exits clean and the Vite warning lines now reference resources/scripts/plugins/i18n.ts, confirming every import resolved through the new path. git log --follow on any moved file walks back through its scripts-v2 history.
This commit is contained in:
Darko Gjorgjijoski
2026-04-07 12:50:16 +02:00
parent 064bdf5395
commit 71388ec6a5
448 changed files with 381 additions and 382 deletions

View File

@@ -0,0 +1,105 @@
<template>
<form id="loginForm" @submit.prevent="onSubmit">
<BaseInputGroup
:error="v$.email.$error && v$.email.$errors[0].$message"
:label="$t('login.enter_email')"
class="mb-4"
required
>
<BaseInput
v-model="formData.email"
:invalid="v$.email.$error"
focus
type="email"
name="email"
@input="v$.email.$touch()"
/>
</BaseInputGroup>
<BaseButton
:loading="isLoading"
:disabled="isLoading"
type="submit"
variant="primary"
>
<div v-if="!isSent">
{{ $t('validation.send_reset_link') }}
</div>
<div v-else>
{{ $t('validation.not_yet') }}
</div>
</BaseButton>
<div class="mt-4 mb-4 text-sm">
<router-link
to="/login"
class="text-sm text-primary-400 hover:text-body"
>
{{ $t('general.back_to_login') }}
</router-link>
</div>
</form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { required, email, helpers } from '@vuelidate/validators'
import { useVuelidate } from '@vuelidate/core'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../../../stores/auth.store'
import { useNotificationStore } from '../../../stores/notification.store'
import { handleApiError } from '../../../utils/error-handling'
interface ForgotPasswordForm {
email: string
}
const notificationStore = useNotificationStore()
const authStore = useAuthStore()
const { t } = useI18n()
const formData = reactive<ForgotPasswordForm>({
email: '',
})
const isSent = ref<boolean>(false)
const isLoading = ref<boolean>(false)
const rules = {
email: {
required: helpers.withMessage(t('validation.required'), required),
email: helpers.withMessage(t('validation.email_incorrect'), email),
},
}
const v$ = useVuelidate(rules, formData)
async function onSubmit(): Promise<void> {
v$.value.$touch()
if (v$.value.$invalid) {
return
}
try {
isLoading.value = true
await authStore.forgotPassword({ email: formData.email })
notificationStore.showNotification({
type: 'success',
message: 'Mail sent successfully',
})
isSent.value = true
} catch (err: unknown) {
const normalized = handleApiError(err)
notificationStore.showNotification({
type: 'error',
message: normalized.message,
})
} finally {
isLoading.value = false
}
}
</script>

View File

@@ -0,0 +1,135 @@
<template>
<form id="loginForm" class="mt-12 text-left" @submit.prevent="onSubmit">
<BaseInputGroup
:error="v$.email.$error && v$.email.$errors[0].$message"
:label="$t('login.email')"
class="mb-4"
required
>
<BaseInput
v-model="authStore.loginData.email"
:invalid="v$.email.$error"
focus
type="email"
name="email"
@input="v$.email.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:error="v$.password.$error && v$.password.$errors[0].$message"
:label="$t('login.password')"
class="mb-4"
required
>
<BaseInput
v-model="authStore.loginData.password"
:invalid="v$.password.$error"
:type="inputType"
name="password"
@input="v$.password.$touch()"
>
<template #right>
<BaseIcon
:name="isShowPassword ? 'EyeIcon' : 'EyeSlashIcon'"
class="mr-1 text-muted cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
</template>
</BaseInput>
</BaseInputGroup>
<div class="mt-5 mb-8">
<div class="mb-4">
<router-link
to="forgot-password"
class="text-sm text-primary-400 hover:text-body"
>
{{ $t('login.forgot_password') }}
</router-link>
</div>
</div>
<BaseButton :loading="isLoading" type="submit">
{{ $t('login.login') }}
</BaseButton>
</form>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { required, email, helpers } from '@vuelidate/validators'
import { useVuelidate } from '@vuelidate/core'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../../../stores/auth.store'
import { useNotificationStore } from '../../../stores/notification.store'
declare global {
interface Window {
demo_mode?: boolean
}
}
const notificationStore = useNotificationStore()
const authStore = useAuthStore()
const { t } = useI18n()
const router = useRouter()
const isLoading = ref<boolean>(false)
const isShowPassword = ref<boolean>(false)
const rules = {
email: {
required: helpers.withMessage(t('validation.required'), required),
email: helpers.withMessage(t('validation.email_incorrect'), email),
},
password: {
required: helpers.withMessage(t('validation.required'), required),
},
}
const v$ = useVuelidate(
rules,
computed(() => authStore.loginData)
)
const inputType = computed<string>(() => {
return isShowPassword.value ? 'text' : 'password'
})
async function onSubmit(): Promise<void> {
v$.value.$touch()
if (v$.value.$invalid) {
return
}
isLoading.value = true
try {
await authStore.login(authStore.loginData)
router.push('/admin/dashboard')
notificationStore.showNotification({
type: 'success',
message: 'Logged in successfully.',
})
} catch (err: unknown) {
const { handleApiError } = await import('../../../utils/error-handling')
const normalized = handleApiError(err)
notificationStore.showNotification({
type: 'error',
message: normalized.message,
})
isLoading.value = false
}
}
onMounted(() => {
if (window.demo_mode) {
authStore.loginData.email = 'demo@invoiceshelf.com'
authStore.loginData.password = 'demo'
}
})
</script>

View File

@@ -0,0 +1,245 @@
<template>
<!-- Loading -->
<div v-if="isLoading" class="mt-12 text-center">
<BaseSpinner class="w-8 h-8 text-primary-400 mx-auto" />
<p class="text-muted mt-4 text-sm">Loading invitation details...</p>
</div>
<!-- Invalid/Expired -->
<div v-else-if="error" class="mt-12 text-center">
<BaseIcon
name="ExclamationCircleIcon"
class="w-12 h-12 mx-auto text-red-400 mb-4"
/>
<h2 class="text-lg font-semibold text-heading mb-2">
Invalid Invitation
</h2>
<p class="text-sm text-muted mb-4">{{ error }}</p>
<router-link
to="/login"
class="text-sm text-primary-400 hover:text-primary-500"
>
Go to Login
</router-link>
</div>
<!-- Registration Form -->
<div v-else class="mt-12">
<div class="mb-8">
<h1 class="text-2xl font-semibold text-heading">
Create Your Account
</h1>
<p class="text-sm text-muted mt-2">
You've been invited to join
<strong class="text-heading">{{ invitationDetails.company_name }}</strong>
as <strong class="text-heading">{{ invitationDetails.role_name }}</strong>
</p>
</div>
<form @submit.prevent="submitRegistration">
<BaseInputGroup
label="Name"
:error="v$.name.$error && v$.name.$errors[0].$message"
class="mb-4"
required
>
<BaseInput
v-model="form.name"
:invalid="v$.name.$error"
focus
@input="v$.name.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup label="Email" class="mb-4">
<BaseInput
v-model="form.email"
type="email"
disabled
/>
</BaseInputGroup>
<BaseInputGroup
label="Password"
:error="v$.password.$error && v$.password.$errors[0].$message"
class="mb-4"
required
>
<BaseInput
v-model="form.password"
:type="isShowPassword ? 'text' : 'password'"
:invalid="v$.password.$error"
@input="v$.password.$touch()"
>
<template #right>
<BaseIcon
:name="isShowPassword ? 'EyeIcon' : 'EyeSlashIcon'"
class="mr-1 text-muted cursor-pointer"
@click="isShowPassword = !isShowPassword"
/>
</template>
</BaseInput>
</BaseInputGroup>
<BaseInputGroup
label="Confirm Password"
:error="
v$.password_confirmation.$error &&
v$.password_confirmation.$errors[0].$message
"
class="mb-4"
required
>
<BaseInput
v-model="form.password_confirmation"
:type="isShowConfirmPassword ? 'text' : 'password'"
:invalid="v$.password_confirmation.$error"
@input="v$.password_confirmation.$touch()"
>
<template #right>
<BaseIcon
:name="isShowConfirmPassword ? 'EyeIcon' : 'EyeSlashIcon'"
class="mr-1 text-muted cursor-pointer"
@click="isShowConfirmPassword = !isShowConfirmPassword"
/>
</template>
</BaseInput>
</BaseInputGroup>
<div class="mt-5 mb-8">
<router-link
to="/login"
class="text-sm text-primary-400 hover:text-body"
>
Already have an account? Log in
</router-link>
</div>
<BaseButton
:loading="isSubmitting"
:disabled="isSubmitting"
type="submit"
>
Create Account & Join
</BaseButton>
</form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { helpers, required, minLength, sameAs } from '@vuelidate/validators'
import { useVuelidate } from '@vuelidate/core'
import { authService } from '../../../api/services/auth.service'
interface InvitationDetailsData {
email: string
company_name: string
role_name: string
}
interface RegistrationForm {
name: string
email: string
password: string
password_confirmation: string
}
const route = useRoute()
const router = useRouter()
const isLoading = ref<boolean>(true)
const isSubmitting = ref<boolean>(false)
const isShowPassword = ref<boolean>(false)
const isShowConfirmPassword = ref<boolean>(false)
const error = ref<string | null>(null)
const invitationDetails = ref<InvitationDetailsData>({
email: '',
company_name: '',
role_name: '',
})
const form = reactive<RegistrationForm>({
name: '',
email: '',
password: '',
password_confirmation: '',
})
const rules = computed(() => ({
name: {
required: helpers.withMessage('Name is required', required),
},
password: {
required: helpers.withMessage('Password is required', required),
minLength: helpers.withMessage('Password must be at least 8 characters', minLength(8)),
},
password_confirmation: {
required: helpers.withMessage('Please confirm your password', required),
sameAs: helpers.withMessage('Passwords do not match', sameAs(form.password)),
},
}))
const v$ = useVuelidate(
rules,
computed(() => form),
)
const token = computed<string>(() => route.query.invitation as string)
onMounted(async () => {
if (!token.value) {
error.value = 'No invitation token provided.'
isLoading.value = false
return
}
try {
const details = await authService.getInvitationDetails(token.value) as unknown as InvitationDetailsData
invitationDetails.value = {
email: details.email,
company_name: details.company_name,
role_name: details.role_name,
}
form.email = details.email
} catch {
error.value = 'This invitation is invalid or has expired.'
} finally {
isLoading.value = false
}
})
async function submitRegistration(): Promise<void> {
v$.value.$touch()
if (v$.value.$invalid) return
isSubmitting.value = true
try {
const response = await authService.registerWithInvitation({
name: form.name,
email: form.email,
password: form.password,
password_confirmation: form.password_confirmation,
invitation_token: token.value,
})
// Save the auth token before navigating (matching old version's pattern)
localStorage.setItem('auth.token', `Bearer ${response.token}`)
router.push('/admin/dashboard')
} catch (err: unknown) {
const { handleApiError } = await import('../../../utils/error-handling')
const { useNotificationStore } = await import('../../../stores/notification.store')
const normalized = handleApiError(err)
const notificationStore = useNotificationStore()
notificationStore.showNotification({
type: 'error',
message: normalized.message,
})
} finally {
isSubmitting.value = false
}
}
</script>

View File

@@ -0,0 +1,169 @@
<template>
<form id="loginForm" @submit.prevent="onSubmit">
<BaseInputGroup
:error="errorEmail"
:label="$t('login.email')"
class="mb-4"
required
>
<BaseInput
v-model="formData.email"
:invalid="v$.email.$error"
focus
type="email"
name="email"
@input="v$.email.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:error="errorPassword"
:label="$t('login.password')"
class="mb-4"
required
>
<BaseInput
v-model="formData.password"
:invalid="v$.password.$error"
type="password"
name="password"
@input="v$.password.$touch()"
/>
</BaseInputGroup>
<BaseInputGroup
:error="errorConfirmPassword"
:label="$t('login.retype_password')"
class="mb-4"
required
>
<BaseInput
v-model="formData.password_confirmation"
:invalid="v$.password_confirmation.$error"
type="password"
name="password_confirmation"
@input="v$.password_confirmation.$touch()"
/>
</BaseInputGroup>
<BaseButton :loading="isLoading" type="submit" variant="primary">
{{ $t('login.reset_password') }}
</BaseButton>
</form>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required, email, minLength, sameAs } from '@vuelidate/validators'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../../../stores/auth.store'
import { useNotificationStore } from '../../../stores/notification.store'
import { handleApiError } from '../../../utils/error-handling'
interface ResetPasswordForm {
email: string
password: string
password_confirmation: string
}
const notificationStore = useNotificationStore()
const authStore = useAuthStore()
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const formData = reactive<ResetPasswordForm>({
email: '',
password: '',
password_confirmation: '',
})
const isLoading = ref<boolean>(false)
const rules = computed(() => ({
email: { required, email },
password: {
required,
minLength: minLength(8),
},
password_confirmation: {
sameAsPassword: sameAs(formData.password),
},
}))
const v$ = useVuelidate(rules, formData)
const errorEmail = computed<string>(() => {
if (!v$.value.email.$error) {
return ''
}
if (v$.value.email.required.$invalid) {
return t('validation.required')
}
if (v$.value.email.email) {
return t('validation.email_incorrect')
}
return ''
})
const errorPassword = computed<string>(() => {
if (!v$.value.password.$error) {
return ''
}
if (v$.value.password.required.$invalid) {
return t('validation.required')
}
if (v$.value.password.minLength) {
return t('validation.password_min_length', {
count: v$.value.password.minLength.$params.min,
})
}
return ''
})
const errorConfirmPassword = computed<string>(() => {
if (!v$.value.password_confirmation.$error) {
return ''
}
if (v$.value.password_confirmation.sameAsPassword.$invalid) {
return t('validation.password_incorrect')
}
return ''
})
async function onSubmit(): Promise<void> {
v$.value.$touch()
if (v$.value.$invalid) {
return
}
try {
isLoading.value = true
await authStore.resetPassword({
email: formData.email,
password: formData.password,
password_confirmation: formData.password_confirmation,
token: route.params.token as string,
})
notificationStore.showNotification({
type: 'success',
message: t('login.password_reset_successfully'),
})
router.push('/login')
} catch (err: unknown) {
const normalized = handleApiError(err)
notificationStore.showNotification({
type: 'error',
message: normalized.message,
})
} finally {
isLoading.value = false
}
}
</script>