2025-10-02 22:52:13 +03:00
|
|
|
<x-guest-layout>
|
2025-10-04 18:52:44 +03:00
|
|
|
<form method="POST" action="{{ route('register') }}" x-data="registerSteps()" x-init="init()" @keydown.enter.prevent="onEnter($event)">
|
2025-10-02 22:52:13 +03:00
|
|
|
@csrf
|
|
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<!-- Step indicator (visually simple) -->
|
|
|
|
|
<div class="mb-6 flex items-center gap-3">
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<div :class="step===1?'bg-indigo-600 text-white':'bg-gray-200 text-gray-700'" class="h-6 w-6 rounded-full flex items-center justify-center text-xs font-bold">1</div>
|
|
|
|
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('app.auth.register.steps.step1_title') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="h-px w-8 bg-gray-300 dark:bg-gray-700"></div>
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<div :class="step===2?'bg-indigo-600 text-white':'bg-gray-200 text-gray-700'" class="h-6 w-6 rounded-full flex items-center justify-center text-xs font-bold">2</div>
|
|
|
|
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ __('app.auth.register.steps.step2_title') }}</div>
|
|
|
|
|
</div>
|
2025-10-02 22:52:13 +03:00
|
|
|
</div>
|
|
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<!-- Step 1: account basics -->
|
|
|
|
|
<div x-show="step===1" x-cloak>
|
|
|
|
|
<!-- Name -->
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="name" :value="__('Name')" />
|
|
|
|
|
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
|
|
|
|
|
<x-input-error :messages="$errors->get('name')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Email Address -->
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
<x-input-label for="email" :value="__('Email')" />
|
|
|
|
|
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
|
|
|
|
|
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Password -->
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
<x-input-label for="password" :value="__('Password')" />
|
|
|
|
|
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
|
|
|
|
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Confirm Password -->
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
|
|
|
|
<x-text-input id="password_confirmation" class="block mt-1 w-full" type="password" name="password_confirmation" required autocomplete="new-password" />
|
|
|
|
|
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-06 22:35:04 +03:00
|
|
|
<!-- Is trainer -->
|
|
|
|
|
<div class="mt-4">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<input id="is_trainer" name="is_trainer" type="checkbox" value="1" @checked(old('is_trainer')) class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
|
|
|
|
<label for="is_trainer" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">{{ __('app.auth.register.is_trainer') }}</label>
|
|
|
|
|
</div>
|
|
|
|
|
<x-input-error :messages="$errors->get('is_trainer')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<div class="flex items-center justify-end mt-6">
|
|
|
|
|
<a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" href="{{ route('login') }}">
|
|
|
|
|
{{ __('Already registered?') }}
|
|
|
|
|
</a>
|
|
|
|
|
<button type="button" @click="goNext()" class="ms-4 inline-flex items-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow hover:bg-indigo-500">{{ __('app.auth.register.steps.next') }}</button>
|
|
|
|
|
</div>
|
2025-10-02 22:52:13 +03:00
|
|
|
</div>
|
|
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<!-- Step 2: fitness profile (optional) -->
|
|
|
|
|
<div x-show="step===2" x-cloak>
|
|
|
|
|
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="age" :value="__('app.auth.register.age')" />
|
|
|
|
|
<x-text-input id="age" class="block mt-1 w-full" type="number" min="5" max="120" name="age" :value="old('age')" />
|
|
|
|
|
<x-input-error :messages="$errors->get('age')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="height_cm" :value="__('app.auth.register.height_cm')" />
|
|
|
|
|
<x-text-input id="height_cm" class="block mt-1 w-full" type="number" step="0.5" min="80" max="250" name="height_cm" :value="old('height_cm')" />
|
|
|
|
|
<x-input-error :messages="$errors->get('height_cm')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="weight_kg" :value="__('app.auth.register.weight_kg')" />
|
|
|
|
|
<x-text-input id="weight_kg" class="block mt-1 w-full" type="number" step="0.1" min="20" max="400" name="weight_kg" :value="old('weight_kg')" />
|
|
|
|
|
<x-input-error :messages="$errors->get('weight_kg')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="fitness_level" :value="__('app.auth.register.fitness_level')" />
|
|
|
|
|
<select id="fitness_level" name="fitness_level" class="block mt-1 w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
|
|
|
|
<option value="">{{ __('app.auth.register.select_optional') }}</option>
|
|
|
|
|
<option value="beginner" @selected(old('fitness_level')==='beginner')>{{ __('app.auth.register.levels.beginner') }}</option>
|
|
|
|
|
<option value="intermediate" @selected(old('fitness_level')==='intermediate')>{{ __('app.auth.register.levels.intermediate') }}</option>
|
|
|
|
|
<option value="advanced" @selected(old('fitness_level')==='advanced')>{{ __('app.auth.register.levels.advanced') }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<x-input-error :messages="$errors->get('fitness_level')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="md:col-span-2">
|
|
|
|
|
<x-input-label for="goal" :value="__('app.auth.register.goal')" />
|
|
|
|
|
<select id="goal" name="goal" class="block mt-1 w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
|
|
|
|
<option value="">{{ __('app.auth.register.select_optional') }}</option>
|
|
|
|
|
<option value="strength" @selected(old('goal')==='strength')>{{ __('app.auth.register.goals.strength') }}</option>
|
|
|
|
|
<option value="endurance" @selected(old('goal')==='endurance')>{{ __('app.auth.register.goals.endurance') }}</option>
|
|
|
|
|
<option value="weight_loss" @selected(old('goal')==='weight_loss')>{{ __('app.auth.register.goals.weight_loss') }}</option>
|
|
|
|
|
<option value="hypertrophy" @selected(old('goal')==='hypertrophy')>{{ __('app.auth.register.goals.hypertrophy') }}</option>
|
|
|
|
|
<option value="mobility" @selected(old('goal')==='mobility')>{{ __('app.auth.register.goals.mobility') }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<x-input-error :messages="$errors->get('goal')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="md:col-span-2">
|
|
|
|
|
<x-input-label for="health_notes" :value="__('app.auth.register.health_notes')" />
|
|
|
|
|
<textarea id="health_notes" name="health_notes" rows="3" class="block mt-1 w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">{{ old('health_notes') }}</textarea>
|
|
|
|
|
<x-input-error :messages="$errors->get('health_notes')" class="mt-2" />
|
|
|
|
|
</div>
|
2025-10-02 22:52:13 +03:00
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<!-- Available equipment icon grid -->
|
|
|
|
|
<div class="md:col-span-2">
|
|
|
|
|
<x-input-label for="available_equipment" :value="__('app.auth.register.available_equipment')" />
|
|
|
|
|
<input type="hidden" id="available_equipment" name="available_equipment" value="{{ old('available_equipment') }}">
|
|
|
|
|
@php
|
|
|
|
|
$equipmentOptions = [
|
|
|
|
|
// Simple, high-contrast stroke-based icons to ensure visibility
|
|
|
|
|
['key' => 'dumbbells', 'icon' => '<path d="M2 10h3v4H2v-4Zm17 0h3v4h-3v-4ZM7 9h3v6H7V9Zm7 0h3v6h-3V9Zm-3 2h2v2h-2v-2Z"/>'],
|
|
|
|
|
['key' => 'barbell', 'icon' => '<path d="M3 10h2v4H3v-4Zm4-2h2v8H7V8Zm6 0h2v8h-2V8Zm6 2h2v4h-2v-4ZM5 11h14v2H5v-2Z"/>'],
|
|
|
|
|
['key' => 'kettlebell', 'icon' => '<path d="M9 9a3 3 0 1 1 6 0v1H9V9Zm-2 6a5 5 0 1 1 10 0v2a3 3 0 0 1-3 3H10a3 3 0 0 1-3-3v-2Z"/>'],
|
|
|
|
|
['key' => 'bands', 'icon' => '<path d="M4 8c3-3 13-3 16 0M4 12c3-3 13-3 16 0M4 16c3-3 13-3 16 0"/>'],
|
|
|
|
|
['key' => 'pullup_bar', 'icon' => '<path d="M3 7h18v2H3V7Zm4 2h2v8H7V9Zm8 0h2v8h-2V9Z"/>'],
|
|
|
|
|
['key' => 'bench', 'icon' => '<path d="M5 13h14v2H5v-2Zm2 2h2v3H7v-3Zm8 0h2v3h-2v-3Z"/>'],
|
|
|
|
|
['key' => 'machines', 'icon' => '<rect x="6" y="6" width="12" height="12" rx="2"/><rect x="9" y="8" width="6" height="8" rx="1"/>'],
|
|
|
|
|
['key' => 'yoga_mat', 'icon' => '<path d="M5 17l9-9a3 3 0 1 1 4 4l-9 9a3 3 0 1 1-4-4Z"/>'],
|
|
|
|
|
['key' => 'treadmill', 'icon' => '<path d="M4 17h10l4-8h2l-5 10H4v-2Zm2-8h6v2H6V9Z"/>'],
|
|
|
|
|
['key' => 'bodyweight', 'icon' => '<circle cx="12" cy="6" r="2"/><path d="M9 10h6l3 5h-2v5h-2v-4h-2v4H10v-5H8l1-2.5V10Z"/>'],
|
|
|
|
|
];
|
|
|
|
|
$oldEquip = collect(explode(',', (string) old('available_equipment')))->map(fn($s)=>trim($s))->filter()->values()->all();
|
|
|
|
|
@endphp
|
|
|
|
|
<div class="mt-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
|
|
|
|
|
@foreach($equipmentOptions as $opt)
|
|
|
|
|
@php $selected = in_array($opt['key'], $oldEquip, true); @endphp
|
|
|
|
|
<button type="button" class="equip-option group flex items-center gap-3 rounded-xl border-2 px-4 py-3 text-base select-none transition transform hover:-translate-y-0.5 hover:shadow focus:outline-none focus:ring-2 focus:ring-indigo-500/60 cursor-pointer {{ $selected ? 'border-indigo-500 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 shadow' : 'border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200' }}" data-key="{{ $opt['key'] }}" aria-pressed="{{ $selected ? 'true' : 'false' }}" aria-label="{{ __('app.auth.register.equipment_options.' . $opt['key']) }}">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-8 w-8 {{ $selected ? 'text-indigo-600' : 'text-gray-500' }}">{!! $opt['icon'] !!}</svg>
|
|
|
|
|
<span class="">{{ __('app.auth.register.equipment_options.' . $opt['key']) }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
@endforeach
|
|
|
|
|
</div>
|
|
|
|
|
<p class="mt-2 text-xs text-gray-500">{{ __('app.auth.register.available_equipment_ph') }}</p>
|
|
|
|
|
<x-input-error :messages="$errors->get('available_equipment')" class="mt-2" />
|
|
|
|
|
</div>
|
2025-10-02 22:52:13 +03:00
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<div>
|
|
|
|
|
<x-input-label for="days_per_week" :value="__('app.auth.register.days_per_week')" />
|
|
|
|
|
<x-text-input id="days_per_week" class="block mt-1 w-full" type="number" min="1" max="7" name="days_per_week" :value="old('days_per_week')" />
|
|
|
|
|
<x-input-error :messages="$errors->get('days_per_week')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<x-input-label for="minutes_per_session" :value="__('app.auth.register.minutes_per_session')" />
|
|
|
|
|
<x-text-input id="minutes_per_session" class="block mt-1 w-full" type="number" min="5" max="600" name="minutes_per_session" :value="old('minutes_per_session')" />
|
|
|
|
|
<x-input-error :messages="$errors->get('minutes_per_session')" class="mt-2" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-10 11:55:11 +03:00
|
|
|
<p class="mt-6 text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
{{ __('app.auth.register.consent_prefix') }}
|
|
|
|
|
<a href="{{ route('legal.terms') }}" target="_blank" class="underline hover:text-gray-700 dark:hover:text-gray-200">{{ __('app.footer.terms') }}</a>
|
|
|
|
|
{{ __('app.auth.register.consent_and') }}
|
|
|
|
|
<a href="{{ route('legal.privacy') }}" target="_blank" class="underline hover:text-gray-700 dark:hover:text-gray-200">{{ __('app.footer.privacy') }}</a>.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between mt-4">
|
2025-10-04 18:52:44 +03:00
|
|
|
<button type="button" @click="goBack()" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800">{{ __('app.auth.register.steps.back') }}</button>
|
|
|
|
|
<x-primary-button class="ms-4">
|
|
|
|
|
{{ __('app.auth.register.steps.register') }}
|
|
|
|
|
</x-primary-button>
|
|
|
|
|
</div>
|
2025-10-02 22:52:13 +03:00
|
|
|
</div>
|
|
|
|
|
|
2025-10-04 18:52:44 +03:00
|
|
|
<script>
|
|
|
|
|
function registerSteps(){
|
|
|
|
|
return {
|
|
|
|
|
step: 1,
|
|
|
|
|
init(){
|
|
|
|
|
// Attach equipment toggles
|
|
|
|
|
const hidden = document.getElementById('available_equipment');
|
|
|
|
|
const selected = new Set((hidden.value||'').split(',').map(s=>s.trim()).filter(Boolean));
|
|
|
|
|
const sync = ()=>{ hidden.value = Array.from(selected).join(','); };
|
|
|
|
|
document.querySelectorAll('.equip-option').forEach(btn=>{
|
|
|
|
|
btn.addEventListener('click', ()=>{
|
|
|
|
|
const key = btn.getAttribute('data-key');
|
|
|
|
|
const isOn = btn.getAttribute('aria-pressed') === 'true';
|
|
|
|
|
if (isOn) {
|
|
|
|
|
btn.setAttribute('aria-pressed','false');
|
|
|
|
|
btn.classList.remove('border-indigo-500','bg-indigo-50','dark:bg-indigo-900/30','text-indigo-700','dark:text-indigo-300','shadow');
|
|
|
|
|
btn.classList.add('border-gray-300','dark:border-gray-700','bg-white','dark:bg-gray-900','text-gray-800','dark:text-gray-200');
|
|
|
|
|
selected.delete(key);
|
|
|
|
|
} else {
|
|
|
|
|
btn.setAttribute('aria-pressed','true');
|
|
|
|
|
btn.classList.add('border-indigo-500','bg-indigo-50','dark:bg-indigo-900/30','text-indigo-700','dark:text-indigo-300','shadow');
|
|
|
|
|
btn.classList.remove('border-gray-300','dark:border-gray-700','bg-white','dark:bg-gray-900','text-gray-800','dark:text-gray-200');
|
|
|
|
|
selected.add(key);
|
|
|
|
|
}
|
|
|
|
|
const icon = btn.querySelector('svg');
|
|
|
|
|
if (icon) {
|
|
|
|
|
icon.classList.toggle('text-indigo-600', !isOn);
|
|
|
|
|
icon.classList.toggle('text-gray-500', isOn);
|
|
|
|
|
}
|
|
|
|
|
sync();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
goNext(){
|
|
|
|
|
// basic HTML5 validity check for step 1
|
|
|
|
|
const name = document.getElementById('name');
|
|
|
|
|
const email = document.getElementById('email');
|
|
|
|
|
const pass = document.getElementById('password');
|
|
|
|
|
const pass2 = document.getElementById('password_confirmation');
|
|
|
|
|
if (name.checkValidity() && email.checkValidity() && pass.checkValidity() && pass2.checkValidity()) {
|
|
|
|
|
this.step = 2;
|
|
|
|
|
// focus first optional field
|
|
|
|
|
setTimeout(()=>{ const age = document.getElementById('age'); if (age) age.focus(); }, 0);
|
|
|
|
|
} else {
|
|
|
|
|
// trigger browser messages
|
|
|
|
|
[name,email,pass,pass2].forEach(i=>!i.checkValidity() && i.reportValidity());
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
goBack(){ this.step = 1; setTimeout(()=>{ const name = document.getElementById('name'); if (name) name.focus(); }, 0); },
|
|
|
|
|
onEnter(e){
|
|
|
|
|
if (this.step===1) { this.goNext(); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2025-10-02 22:52:13 +03:00
|
|
|
</form>
|
|
|
|
|
</x-guest-layout>
|