Skip to content

Generate Password Page ​

File Information ​

  • Path: app/pages/[brand_name]/generate-password.vue
  • Route: /{brand_name}/generate-password
  • Purpose: Password generation page for new users via invitation link
  • Layout: login-layout
  • Middleware: check-url, redirect-if-logged-in

Overview ​

The Generate Password page allows new users to set their password when they receive an invitation link. Users enter their name and password, and the system creates their account with the provided credentials.

Key Features ​

  1. Invitation Token Validation

    • Validates invitation token from URL query
    • Fetches invitation details from API
    • Handles expired/invalid tokens
    • Error state display
  2. Form Fields

    • Name input (pre-filled from invitation)
    • Password input with validation
    • Confirm password input with validation
    • Email display (read-only, from invitation)
  3. Form Validation

    • Name required validation
    • Password required and minimum length (6 characters)
    • Confirm password match validation
    • Real-time error display
    • Submit button state management
  4. Error Handling

    • Invalid/expired token handling
    • Form validation errors
    • API error handling
    • User-friendly error messages
  5. Timezone Support

    • Automatically detects user timezone
    • Sends timezone with password generation request

Components Used ​

  • v-text-field - Vuetify text input components
  • v-btn - Vuetify button component
  • v-progress-circular - Loading spinner
  • v-skeleton-loader - Skeleton loader for initial state
  • NuxtLink - Nuxt link component
  • AsyncIcon - Async icon component

Composables Used ​

  • useAppDataStore() - Application data store
  • useSnackbar() - Snackbar notifications
  • useHelpers() - Helper functions

Data Properties ​

Reactive Data ​

typescript
{
  form: {
    name: string,                    // User full name
    email: string,                   // User email (from invitation)
    password: string,                // New password
    confirm_password: string,        // Confirm password
    logo: string,                    // Brand logo
    reset_token: string,             // Reset token (from invitation)
    invitation_token: string,        // Invitation token (from URL)
    timezone: string                 // User timezone
  },
  contentLoading: boolean,           // Initial content loading state
  submitting: boolean,               // Form submission loading state
  error: boolean,                    // Error state flag
  message: string,                   // Error message
  nameError: string | null,          // Name validation error
  passwordError: string | null,      // Password validation error
  confirmPasswordError: string | null // Confirm password validation error
}

Computed Properties ​

brandName ​

Returns brand name from route parameters.

typescript
computed(() => 
  (route.params.brand_name as string) || (route.query.brand_name as string)
)

disableSubmitBtn ​

Determines if submit button should be disabled.

typescript
computed(() => {
  const hasErrors = 
    !!nameError.value || 
    !!passwordError.value || 
    !!confirmPasswordError.value
  const emptyFields = 
    !form.value.name.trim() ||
    !form.value.password ||
    !form.value.confirm_password
  return hasErrors || emptyFields || submitting.value
})

Returns: boolean - True if form is invalid or loading

Methods ​

validateName() ​

Validates name required field.

typescript
function validateName() {
  if (!form.value.name.trim()) {
    nameError.value = "Name is required"
    return
  }
  nameError.value = null
}

Validation Rules:

  • Name is required
  • Sets nameError on validation failure

validatePassword() ​

Validates password required field and minimum length.

typescript
function validatePassword() {
  if (!form.value.password) {
    passwordError.value = "Password is required"
    return
  }
  if (form.value.password.length < 6) {
    passwordError.value = "Password must be at least 6 characters"
    return
  }
  passwordError.value = null
}

Validation Rules:

  • Password is required
  • Minimum 6 characters
  • Sets passwordError on validation failure

validateConfirmPassword() ​

Validates confirm password matches password.

typescript
function validateConfirmPassword() {
  if (!form.value.confirm_password) {
    confirmPasswordError.value = "Confirm Password is required"
    return
  }
  if (form.value.confirm_password !== form.value.password) {
    confirmPasswordError.value = 
      "Password and Confirm Password did not match."
    return
  }
  confirmPasswordError.value = null
}

Validation Rules:

  • Confirm password is required
  • Must match password exactly
  • Sets confirmPasswordError on validation failure

handleSubmit() ​

Handles password generation form submission.

typescript
async function handleSubmit() {
  validateName()
  validatePassword()
  validateConfirmPassword()
  
  if (disableSubmitBtn.value) return
  
  submitting.value = true
  
  try {
    const payload = {
      email_token: form.value.reset_token,
      token: form.value.invitation_token,
      password: form.value.password,
      name: form.value.name,
      timezone: form.value.timezone,
    }
    
    const response = await $api("generate-password", {
      method: "POST",
      body: payload,
      skipAuth: true,
    })
    
    const url = response.data?.data?.user?.url
    const successUrl = url || brandName.value
    
    snackbar.showSuccess(
      response.data?.message ||
      response.data?.data?.message ||
      response.message ||
      "Password generated successfully!"
    )
    
    await navigateTo(`/${successUrl}/login`)
  } catch (err: any) {
    snackbar.showError(err.data?.message || "Something went wrong")
    setTimeout(() => {
      navigateTo(`/${brandName.value}/login`)
    }, 5000)
  } finally {
    submitting.value = false
  }
}

Flow:

  1. Validates all form fields
  2. Checks if form is valid
  3. Builds payload with password, name, tokens, and timezone
  4. Calls generate password API
  5. Extracts user URL from response
  6. Shows success message
  7. Redirects to login page (with user's brand URL if available)
  8. Handles errors with redirect after 5 seconds

User Interactions ​

  1. Form Input

    • Edit name (pre-filled from invitation)
    • Enter password
    • Enter confirm password
    • Real-time validation on input
  2. Form Submission

    • Click "Generate Password" button
    • Form validates all fields before submission
    • Loading state during API call
  3. Navigation

    • Click "Back to Login" link to navigate to login page
    • Automatic redirect to login after success
    • Error redirect to login after 5 seconds

Styling ​

  • Uses scoped styles from @/assets/scss/login.scss
  • Form layout styles
  • Input field styles with validation states
  • Button styles with loading states
  • Error message styles
  • Skeleton loader styles

Lifecycle Hooks ​

onMounted ​

Initializes content loading state and fetches invitation details.

typescript
onMounted(async () => {
  nextTick(() => (contentLoading.value = false))
})

useAsyncData (Server-Side) ​

Fetches invitation details on server-side.

typescript
const { data: invitationData } = await useAsyncData(
  "invitation-data",
  async () => {
    const invitation_token = route.query.invitation_token as string
    if (!invitation_token) throw createError({ statusCode: 404 })
    
    try {
      const response = await $api("get-invitation-details", {
        method: "POST",
        body: { invitation_token },
        skipAuth: true,
      })
      
      if (!response.status) {
        throw createError({
          statusCode: 400,
          statusMessage: response.message || "Invalid invitation",
        })
      }
      
      return response
    } catch (err: any) {
      const status = err.response?.status || 500
      if (err.data?.message?.includes("link is expired")) {
        error.value = true
        message.value = err.data.message
      } else {
        throw createError({
          statusCode: status,
          statusMessage: err.data?.message || err.message,
        })
      }
    }
  }
)

Purpose: Validates invitation token and pre-fills form with user data.

API Endpoints ​

Get Invitation Details ​

  • Endpoint: POST /get-invitation-details (via $api())
  • Request Body:
    typescript
    {
      invitation_token: string
    }
  • Response:
    typescript
    {
      status: boolean,
      data: {
        name: string,
        email: string,
        reset_token: string,
        invitation_token: string
      },
      message?: string
    }

Generate Password ​

  • Endpoint: POST /generate-password (via $api())
  • Request Body:
    typescript
    {
      email_token: string,
      token: string,
      password: string,
      name: string,
      timezone: string
    }
  • Response:
    typescript
    {
      data: {
        data: {
          message: string,
          user: {
            url: string
          }
        },
        message: string
      }
    }

User Flow ​

  1. User clicks invitation link with invitation_token query parameter
  2. Page validates invitation token on server-side
  3. If token is invalid/expired:
    • Error message displayed
    • Link to login page shown
  4. If token is valid:
    • Form displayed with pre-filled name and email
    • User enters password and confirm password
    • Form validates on input
  5. User clicks "Generate Password" button
  6. System creates account with provided credentials
  7. On success:
    • Success message displayed
    • Redirect to login page (with user's brand URL if available)
  8. On failure:
    • Error message displayed
    • Redirect to login after 5 seconds

Security Considerations ​

  • Invitation token required in URL query
  • Token validated on server-side
  • Expired tokens show error message
  • Password minimum length enforced (6 characters)
  • Password confirmation required
  • Timezone automatically detected and sent

Notes for Development ​

  • Page requires invitation_token query parameter
  • Uses useAsyncData for server-side token validation
  • Brand logo fetched from appData store
  • Timezone automatically detected using dayjs
  • Form pre-filled with invitation data
  • Error state shows different UI (no form, just error message)
  • Success redirect uses user's brand URL if available
  • Error redirect happens after 5 seconds delay