feat: add City10 platform with Laravel 13, AI agents, 3D viewer, and LGPD compliance
Initial commit of the complete City10 virtual city simulation platform. Backend (Laravel 13): - REST API with Sanctum tokens and session-based web authentication - Role-based access control (admin/user) with middleware protection - AI agent CRUD with OpenRouter.ai integration restricted to free models - Store management with 3D coordinates and AI shopkeeper assignment - Versioned legal documents (Terms, Privacy, Cookies) with LGPD audit trail - Math CAPTCHA on public forms and multi-language support (pt_BR, en, es) Frontend: - Responsive Blade templates with Tailwind CSS v4 - Three.js 3D city viewer with SSE chat streaming to AI agents - Admin panel for store and user administration Infrastructure: - MariaDB/MySQL database with comprehensive migrations - Database seeders for demo content and test accounts - PHPUnit test suite and Laravel Pint code styling - Vite build configuration with hot reload
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Login'))
|
||||
|
||||
@section('content')
|
||||
<div style="max-width: 420px; margin: 0 auto;">
|
||||
<div class="card">
|
||||
<h2 style="margin-top:0; color:#fff;">{{ __('Login') }}</h2>
|
||||
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<div class="field">
|
||||
<label for="email">{{ __('Email') }}</label>
|
||||
<input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus autocomplete="username">
|
||||
@error('email')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="password">{{ __('Password') }}</label>
|
||||
<div class="pw-wrap">
|
||||
<input id="password" type="password" name="password" required autocomplete="current-password">
|
||||
<button type="button" class="eye-toggle" data-eye="password" aria-label="Mostrar senha">👁️</button>
|
||||
</div>
|
||||
@error('password')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="field" style="display:flex; align-items:center; gap:.5rem;">
|
||||
<input type="checkbox" id="remember" name="remember" value="1" @checked(old('remember'))>
|
||||
<label for="remember" style="margin:0;">{{ __('Remember Me') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="captcha">{{ $captcha['question'] }}</label>
|
||||
<input id="captcha" type="number" name="captcha" required inputmode="numeric">
|
||||
@error('captcha')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" style="width:100%; padding:.8rem;">{{ __('Login') }}</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-top:1rem; font-size:.9rem; color:var(--muted);">
|
||||
{{ __('welcome.no_account') }} <a href="{{ route('register') }}">{{ __('Register') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Eye icon toggles password visibility on the login form.
|
||||
document.querySelectorAll('.eye-toggle').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var input = document.getElementById(btn.dataset.eye);
|
||||
if (!input) return;
|
||||
var showing = input.type === 'text';
|
||||
input.type = showing ? 'password' : 'text';
|
||||
btn.textContent = showing ? '👁️' : '🙈';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,80 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Register'))
|
||||
|
||||
@section('content')
|
||||
<div style="max-width: 460px; margin: 0 auto;">
|
||||
<div class="card">
|
||||
<h2 style="margin-top:0; color:#fff;">{{ __('Register') }}</h2>
|
||||
|
||||
<form method="POST" action="{{ route('register') }}">
|
||||
@csrf
|
||||
|
||||
<div class="field">
|
||||
<label for="name">{{ __('Name') }}</label>
|
||||
<input id="name" type="text" name="name" value="{{ old('name') }}" required autofocus maxlength="120">
|
||||
@error('name')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="email">{{ __('Email') }}</label>
|
||||
<input id="email" type="email" name="email" value="{{ old('email') }}" required autocomplete="username">
|
||||
@error('email')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="password">{{ __('Password') }}</label>
|
||||
<div class="pw-wrap">
|
||||
<input id="password" type="password" name="password" required autocomplete="new-password">
|
||||
<button type="button" class="eye-toggle" data-eye="password" aria-label="Mostrar senha">👁️</button>
|
||||
</div>
|
||||
@error('password')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="password_confirmation">{{ __('Password Confirmation') }}</label>
|
||||
<div class="pw-wrap">
|
||||
<input id="password_confirmation" type="password" name="password_confirmation" required autocomplete="new-password">
|
||||
<button type="button" class="eye-toggle" data-eye="password_confirmation" aria-label="Confirmar senha">👁️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="locale">Idioma / Language / Idioma</label>
|
||||
<select id="locale" name="locale">
|
||||
<option value="pt_BR" @selected(old('locale', 'pt_BR') === 'pt_BR')>Português (Brasil)</option>
|
||||
<option value="en" @selected(old('locale') === 'en')>English</option>
|
||||
<option value="es" @selected(old('locale') === 'es')>Español</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="captcha">{{ $captcha['question'] }}</label>
|
||||
<input id="captcha" type="number" name="captcha" required inputmode="numeric">
|
||||
@error('captcha')<div class="error">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" style="width:100%; padding:.8rem;">{{ __('Register') }}</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-top:1rem; font-size:.9rem; color:var(--muted);">
|
||||
{{ __('welcome.has_account') }} <a href="{{ route('login') }}">{{ __('Login') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Eye icons toggle password visibility on the registration form.
|
||||
document.querySelectorAll('.eye-toggle').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var input = document.getElementById(btn.dataset.eye);
|
||||
if (!input) return;
|
||||
var showing = input.type === 'text';
|
||||
input.type = showing ? 'password' : 'text';
|
||||
btn.textContent = showing ? '👁️' : '🙈';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user