Files
fitness/resources/views/auth/register.blade.php
T

228 lines
17 KiB
PHP
Raw Normal View History

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>
<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>