Commit 100e6ffe authored by Mahmoud Aglan's avatar Mahmoud Aglan

init

parents
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[{compose,docker-compose}.{yml,yaml}]
indent_size = 4
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.codex
/.cursor/
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/fonts-manifest.dev.json
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
_ide_helper.php
Homestead.json
Homestead.yaml
Thumbs.db
ignore-scripts=true
audit=true
Welcome to the EL-Captain Brand Identity & Design System presentation. As your branding agency, we have approached this project with a singular vision: to position EL-Captain as the definitive "Command Center" for membership-based organizations.
We have stripped away any clichéd sports imagery to focus on what matters to enterprise clients and academy founders: Trust, Navigation, Control, and Scale. The following comprehensive brand identity is designed to stand alongside industry giants like Stripe, Linear, and Atlassian.
1. Brand Strategy & Personality
The Core Idea: The Modern Operating System for Leadership.
EL-Captain is not just management software; it is the silent partner that brings order to chaos. It guides organizations to operate with precision.
Brand Archetype: The Ruler / The Sage (Authoritative, organized, analytical, and empowering).
Voice: Clear, concise, confident, and professional.
Keywords: Command, Navigation, Trust, Precision, Enterprise, Connected.
2. Logo Concepts & Rationale
We have developed three distinct directions for the EL-Captain visual identity. All are flat, geometric, scalable, and function perfectly in monochrome.
Concept 1: The Commander's Compass (Recommended)
The Mark: A minimalist, geometric monogram integrating the letter 'C' with a subtle, abstract compass needle in the negative space. The needle points 'North-East' (up and to the right), universally symbolizing growth, forward momentum, and navigation.
The Wordmark: Clean, custom-tracked sans-serif with a slightly bolder weight on "EL" to anchor the name.
Rationale: Captures the essence of a "Captain" navigating a ship. It communicates leadership and direction without relying on literal maritime or sports imagery.
Concept 2: The Vanguard Shield
The Mark: A highly simplified, tech-forward shield constructed from two interlocking 'L' shapes (nodding to EL and leadership) that form a protective, solid 'C' in the center.
Rationale: Speaks directly to data security, trust, and organizational integrity—crucial for enterprise clients managing thousands of members and financial data.
Concept 3: The Connected Nodes
The Mark: Three distinct geometric pillars (representing Members, Staff, and Facilities) seamlessly connected by a singular sweeping line that forms the letter 'E'.
Rationale: Focuses on the "All-in-One" nature of the CRM/ERP system, emphasizing community, organization, and seamless integration.
Iconography & Sizing Rules
App Icon: The standalone mark (Compass, Shield, or Nodes) placed precisely in the center of a solid Midnight Blue square with a subtle 10% corner radius.
Favicon: A stripped-down, thickened version of the mark optimized for 16x16 pixel rendering.
Clear Space: The minimum clear space around the logo must be equal to the height of the "C" in the wordmark.
3. Color System
We have selected a variation of Option D, optimized for modern SaaS interfaces. It exudes enterprise authority while maintaining a sharp, modern technological edge.
Core Brand Palette
Color Name HEX Code RGB Value Usage
Midnight Blue #0B132B 11, 19, 43 Primary brand color, primary text, dark mode backgrounds.
Electric Blue #1D4ED8 29, 78, 216 Primary CTA buttons, active states, key brand accents.
Cloud White #FFFFFF 255, 255, 255 Light mode backgrounds, text on dark buttons.
Slate Gray #64748B 100, 116, 139 Secondary text, inactive icons, subtle borders.
Semantic & UI Palette (Light / Dark Mode)
Semantic Role Light Mode (HEX) Dark Mode (HEX) Purpose
Success #10B981 (Emerald) #059669 Payment confirmed, attendance logged.
Warning #F59E0B (Amber) #D97706 Pending invoices, expiring memberships.
Error #EF4444 (Rose) #DC2626 Failed payments, system errors.
Information #3B82F6 (Blue) #2563EB Tooltips, general system notifications.
Surface #F8FAFC (Cool White) #1E293B Card backgrounds, sidebar panels.
Borders #E2E8F0 (Light Gray) #334155 Dividers, table borders, input outlines.
Accessibility Note: Electric Blue (#1D4ED8) on Cloud White (#FFFFFF) provides a contrast ratio of 7.1:1, easily passing WCAG AAA standards for normal text.
4. Typography System
We recommend a dual-font system that balances marketing appeal with extreme dashboard utility.
Primary/UI Font: Inter
Why: Inter is the gold standard for dense, data-heavy dashboards. Its tall x-height and exceptional legibility at small sizes (like 11px table data) make it perfect for the EL-Captain ERP/CRM features.
Display/Marketing Font: Plus Jakarta Sans
Why: Used for headings, landing pages, and the logo wordmark. It has a geometric, modern tech feel (similar to Stripe and Linear) that commands attention and feels premium.
5. UI Design Language
The UI must feel like a high-end financial or productivity tool—quiet, organized, and frictionless.
Cards & Containers: Flat design. Use Surface colors rather than heavy drop shadows to distinguish cards.
Shadows: Extremely subtle. Only use drop shadows for floating elements (dropdowns, modals, popovers) to establish z-index hierarchy.
Border Radius: 6px for buttons and inputs. 12px for larger cards and modals. This slight rounding feels modern but retains an architectural, structured edge.
Input Fields & Forms: Minimalist. A 1px solid border (#E2E8F0), transitioning to a 2px Electric Blue border on focus. Labels should be crisp, uppercase, and tracked out.
Tables: The core of the software. Striped rows should be avoided; instead, use generous padding (16px vertical) and thin, 1px horizontal dividers to maintain a breathable layout.
Grid & Spacing: Based on a strict 8pt grid system. Spacing should be predictable (8, 16, 24, 32, 48, 64) to create a rhythmic, orderly interface.
Motion Principles: Snappy and functional. Transitions (hover states, modal opens) should be quick (150-200ms) and use an ease-out curve. No bouncy or playful animations.
6. Conceptual Deliverables & Mockups
To visualize the brand in action, here is the creative direction for the primary brand touchpoints:
Dashboard Appearance
A persistent left-hand sidebar in Midnight Blue featuring crisp, monochrome linear icons for navigation (Members, Finance, Scheduling). The main work area is Cloud White featuring a grid of metric cards (Total Revenue, Active Members). A large, clean data table dominates the center, utilizing Inter for absolute clarity. Data visualizations (charts) utilize gradients of Electric Blue and Slate Gray.
Landing Page Hero Concept
Headline: The Command Center for Modern Organizations. (Plus Jakarta Sans, Midnight Blue)
Sub-headline: Everything you need to manage members, scale operations, and lead your club—in one intelligent platform.
Visual: A massive, high-fidelity, edge-to-edge mockup of the dashboard, angled slightly to show depth, glowing softly against a clean, off-white background. A single, bold Electric Blue primary CTA button reads: "Request Enterprise Demo".
Mobile App Concept
The app prioritizes on-the-go actions. The bottom navigation bar features cleanly stroked icons. The home screen offers a "Quick Action" FAB (Floating Action Button) for taking attendance or scanning member QR codes. The interface relies entirely on typography and white space for hierarchy, avoiding heavy colored headers.
Brand Rules: Do's and Don'ts
DO: Ensure the logo has ample breathing room.
DO: Use the solid white logo on Midnight Blue or photography with a dark overlay.
DON'T: Add drop shadows or gradients to the primary logo mark.
DON'T: Use generic clip-art icons (whistles, soccer balls, clipboards) in the UI or marketing materials.
DON'T: Mix the semantic colors (e.g., don't use the Error Red for a marketing highlight).
\ No newline at end of file
# El Captain Sports Management
## What This Is
A modular ERP specialized for sports organizations. Activity-agnostic platform — any sport/activity is a configuration of the same underlying business model.
## Stack
- **Framework:** Laravel 13 (PHP 8.5)
- **Database:** PostgreSQL 16 at `18.192.166.221:5434` (db: `elcaptainsportsonly`, user: `elcaptain`)
- **Frontend:** Livewire 3 + Alpine.js + Tailwind CSS (to be added)
- **Multi-tenancy:** Single database with `academy_id` column + global scope
- **Deploy:** CapRover at `*.caprover.al-arcade.com`
## Architecture
### Domain-Driven Layout
```
app/Domain/{Module}/Models/ — Eloquent models
app/Domain/{Module}/Services/ — Business logic (DB::transaction wrapped)
app/Domain/{Module}/Events/ — Domain events (dispatched after commit)
app/Domain/{Module}/Enums/ — PHP 8.1+ backed enums
app/Domain/Shared/Traits/ — BelongsToAcademy, HasUuid
```
### Key Patterns
- **BelongsToAcademy trait** — auto-scopes queries to `app('current_academy')`
- **HasUuid trait** — auto-generates UUID, uses `uuid` as route key
- **Amounts stored as integers** (piasters, not pounds) to avoid float issues
- **Double-entry transactions** — every financial operation creates debit + credit entries
- **Events for side effects** — never send email/notification inline from services
### Modules (in build order)
1. Financial (DONE - schema + models + services)
2. People (next)
3. Training Programs + Groups
4. Facility & Scheduling
5. Attendance
6. Inventory
7. Pricing Engine
## Commands
```bash
php artisan serve # Dev server at :8000
php artisan migrate # Run migrations
php artisan db:seed # Seed default data
php artisan tinker # REPL
```
## Login Credentials (dev)
- Email: `admin@elcaptain.com`
- Password: `password`
## Rules
- Laravel Discipline is ALWAYS active (see global CLAUDE.md)
- Migration first — schema is source of truth
- One vertical slice at a time
- Arabic locale default (`ar`), bilingual (name + name_ar)
- CHECK constraints on all status/type columns
- No `href="#"` — real routes or don't render the link
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals.
## Agentic Development
Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow:
```bash
composer require laravel/boost --dev
php artisan boost:install
```
Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices.
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
<?php
namespace App\Domain\Financial\Enums;
enum AccountType: string
{
case Asset = 'asset';
case Liability = 'liability';
case Equity = 'equity';
case Revenue = 'revenue';
case Expense = 'expense';
}
<?php
namespace App\Domain\Financial\Enums;
enum InvoiceStatus: string
{
case Draft = 'draft';
case Pending = 'pending';
case AwaitingApproval = 'awaiting_approval';
case Sent = 'sent';
case Paid = 'paid';
case PartiallyPaid = 'partially_paid';
case Overpaid = 'overpaid';
case Overdue = 'overdue';
case Cancelled = 'cancelled';
case Refunded = 'refunded';
case PartiallyRefunded = 'partially_refunded';
case WrittenOff = 'written_off';
case Disputed = 'disputed';
public function label(): string
{
return match ($this) {
self::Draft => 'مسودة',
self::Pending => 'معلق',
self::AwaitingApproval => 'في انتظار الموافقة',
self::Sent => 'مرسلة',
self::Paid => 'مدفوعة',
self::PartiallyPaid => 'مدفوعة جزئياً',
self::Overpaid => 'مدفوعة بالزيادة',
self::Overdue => 'متأخرة',
self::Cancelled => 'ملغاة',
self::Refunded => 'مستردة',
self::PartiallyRefunded => 'مستردة جزئياً',
self::WrittenOff => 'شُطبت',
self::Disputed => 'متنازع عليها',
};
}
}
<?php
namespace App\Domain\Financial\Enums;
enum PaymentMethod: string
{
case Cash = 'cash';
case Card = 'card';
case BankTransfer = 'bank_transfer';
case Wallet = 'wallet';
case Online = 'online';
case Cheque = 'cheque';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::Cash => 'نقدي',
self::Card => 'بطاقة',
self::BankTransfer => 'تحويل بنكي',
self::Wallet => 'محفظة',
self::Online => 'إلكتروني',
self::Cheque => 'شيك',
self::Other => 'أخرى',
};
}
}
<?php
namespace App\Domain\Financial\Enums;
enum PaymentStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Failed = 'failed';
case Cancelled = 'cancelled';
case Refunded = 'refunded';
public function label(): string
{
return match ($this) {
self::Pending => 'معلق',
self::Confirmed => 'مؤكد',
self::Failed => 'فشل',
self::Cancelled => 'ملغى',
self::Refunded => 'مسترد',
};
}
}
<?php
namespace App\Domain\Financial\Enums;
enum TransactionType: string
{
case PaymentReceived = 'payment_received';
case PaymentMade = 'payment_made';
case Refund = 'refund';
case Transfer = 'transfer';
case Adjustment = 'adjustment';
case Fee = 'fee';
case Discount = 'discount';
case WriteOff = 'write_off';
case OpeningBalance = 'opening_balance';
}
<?php
namespace App\Domain\Financial\Events;
use App\Domain\Financial\Models\Invoice;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class InvoiceCreated
{
use Dispatchable, SerializesModels;
public function __construct(public Invoice $invoice) {}
}
<?php
namespace App\Domain\Financial\Events;
use App\Domain\Financial\Models\Invoice;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class InvoicePaid
{
use Dispatchable, SerializesModels;
public function __construct(public Invoice $invoice) {}
}
<?php
namespace App\Domain\Financial\Events;
use App\Domain\Financial\Models\Payment;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PaymentReceived
{
use Dispatchable, SerializesModels;
public function __construct(public Payment $payment) {}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Financial\Enums\AccountType;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class FinancialAccount extends Model
{
use HasUuid, BelongsToAcademy;
protected $fillable = [
'academy_id',
'parent_id',
'code',
'name',
'name_ar',
'type',
'category',
'is_system',
'is_active',
'description',
];
protected function casts(): array
{
return [
'type' => AccountType::class,
'is_system' => 'boolean',
'is_active' => 'boolean',
];
}
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
public function debitTransactions(): HasMany
{
return $this->hasMany(Transaction::class, 'debit_account_id');
}
public function creditTransactions(): HasMany
{
return $this->hasMany(Transaction::class, 'credit_account_id');
}
}
<?php
namespace App\Domain\Financial\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Installment extends Model
{
protected $fillable = [
'payment_plan_id',
'payment_id',
'sequence',
'amount',
'due_date',
'status',
'paid_at',
];
protected function casts(): array
{
return [
'sequence' => 'integer',
'amount' => 'integer',
'due_date' => 'date',
'paid_at' => 'datetime',
];
}
public function paymentPlan(): BelongsTo
{
return $this->belongsTo(PaymentPlan::class);
}
public function payment(): BelongsTo
{
return $this->belongsTo(Payment::class);
}
public function isOverdue(): bool
{
return $this->status === 'pending' && $this->due_date->isPast();
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Invoice extends Model
{
use HasUuid, BelongsToAcademy, SoftDeletes;
protected $fillable = [
'academy_id',
'number',
'type',
'status',
'billable_type',
'billable_id',
'contact_name',
'contact_phone',
'contact_email',
'subtotal_amount',
'discount_amount',
'tax_amount',
'total_amount',
'paid_amount',
'due_amount',
'currency',
'issue_date',
'due_date',
'paid_at',
'cancelled_at',
'notes',
'metadata',
'created_by',
'approved_by',
];
protected function casts(): array
{
return [
'status' => InvoiceStatus::class,
'subtotal_amount' => 'integer',
'discount_amount' => 'integer',
'tax_amount' => 'integer',
'total_amount' => 'integer',
'paid_amount' => 'integer',
'due_amount' => 'integer',
'issue_date' => 'date',
'due_date' => 'date',
'paid_at' => 'datetime',
'cancelled_at' => 'datetime',
'metadata' => 'array',
];
}
public function billable(): MorphTo
{
return $this->morphTo();
}
public function items(): HasMany
{
return $this->hasMany(InvoiceItem::class);
}
public function payments(): HasMany
{
return $this->hasMany(Payment::class);
}
public function paymentPlan(): HasOne
{
return $this->hasOne(PaymentPlan::class);
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function approver(): BelongsTo
{
return $this->belongsTo(User::class, 'approved_by');
}
public function recalculateTotals(): void
{
$this->subtotal_amount = $this->items()->sum('total_amount');
$this->total_amount = $this->subtotal_amount - $this->discount_amount + $this->tax_amount;
$this->due_amount = $this->total_amount - $this->paid_amount;
$this->save();
}
}
<?php
namespace App\Domain\Financial\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class InvoiceItem extends Model
{
protected $fillable = [
'invoice_id',
'itemable_type',
'itemable_id',
'description',
'quantity',
'unit_price',
'discount_amount',
'tax_amount',
'total_amount',
'metadata',
];
protected function casts(): array
{
return [
'quantity' => 'integer',
'unit_price' => 'integer',
'discount_amount' => 'integer',
'tax_amount' => 'integer',
'total_amount' => 'integer',
'metadata' => 'array',
];
}
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
public function itemable(): MorphTo
{
return $this->morphTo();
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Financial\Enums\PaymentMethod;
use App\Domain\Financial\Enums\PaymentStatus;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Payment extends Model
{
use HasUuid, BelongsToAcademy, SoftDeletes;
protected $fillable = [
'academy_id',
'invoice_id',
'reference',
'direction',
'method',
'status',
'payer_type',
'payer_id',
'amount',
'currency',
'payment_date',
'confirmed_at',
'failed_at',
'notes',
'gateway_data',
'metadata',
'received_by',
'created_by',
];
protected function casts(): array
{
return [
'method' => PaymentMethod::class,
'status' => PaymentStatus::class,
'amount' => 'integer',
'payment_date' => 'date',
'confirmed_at' => 'datetime',
'failed_at' => 'datetime',
'gateway_data' => 'array',
'metadata' => 'array',
];
}
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
public function payer(): MorphTo
{
return $this->morphTo();
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
public function receiver(): BelongsTo
{
return $this->belongsTo(User::class, 'received_by');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function isInbound(): bool
{
return $this->direction === 'inbound';
}
public function isConfirmed(): bool
{
return $this->status === PaymentStatus::Confirmed;
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class PaymentPlan extends Model
{
use HasUuid, BelongsToAcademy;
protected $fillable = [
'academy_id',
'invoice_id',
'status',
'total_installments',
'paid_installments',
'installment_amount',
'frequency',
'start_date',
'next_due_date',
'notes',
];
protected function casts(): array
{
return [
'total_installments' => 'integer',
'paid_installments' => 'integer',
'installment_amount' => 'integer',
'start_date' => 'date',
'next_due_date' => 'date',
];
}
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
public function installments(): HasMany
{
return $this->hasMany(Installment::class);
}
public function isComplete(): bool
{
return $this->paid_installments >= $this->total_installments;
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Financial\Enums\TransactionType;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Transaction extends Model
{
use HasUuid, BelongsToAcademy;
protected $fillable = [
'academy_id',
'debit_account_id',
'credit_account_id',
'payment_id',
'invoice_id',
'reference_type',
'reference_id',
'amount',
'currency',
'type',
'description',
'transaction_date',
'metadata',
'created_by',
];
protected function casts(): array
{
return [
'type' => TransactionType::class,
'amount' => 'integer',
'transaction_date' => 'date',
'metadata' => 'array',
];
}
public function debitAccount(): BelongsTo
{
return $this->belongsTo(FinancialAccount::class, 'debit_account_id');
}
public function creditAccount(): BelongsTo
{
return $this->belongsTo(FinancialAccount::class, 'credit_account_id');
}
public function payment(): BelongsTo
{
return $this->belongsTo(Payment::class);
}
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
public function reference(): MorphTo
{
return $this->morphTo();
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Wallet extends Model
{
use HasUuid, BelongsToAcademy;
protected $fillable = [
'academy_id',
'owner_type',
'owner_id',
'balance',
'currency',
'is_active',
];
protected function casts(): array
{
return [
'balance' => 'integer',
'is_active' => 'boolean',
];
}
public function owner(): MorphTo
{
return $this->morphTo();
}
public function walletTransactions(): HasMany
{
return $this->hasMany(WalletTransaction::class);
}
public function formattedBalance(): string
{
return number_format($this->balance / 100, 2) . ' ' . $this->currency;
}
}
<?php
namespace App\Domain\Financial\Models;
use App\Domain\Shared\Traits\HasUuid;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class WalletTransaction extends Model
{
use HasUuid;
protected $fillable = [
'wallet_id',
'type',
'direction',
'amount',
'balance_before',
'balance_after',
'reference_type',
'reference_id',
'description',
'metadata',
'created_by',
];
protected function casts(): array
{
return [
'amount' => 'integer',
'balance_before' => 'integer',
'balance_after' => 'integer',
'metadata' => 'array',
];
}
public function wallet(): BelongsTo
{
return $this->belongsTo(Wallet::class);
}
public function reference(): MorphTo
{
return $this->morphTo();
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}
<?php
namespace App\Domain\Financial\Services;
use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Events\InvoiceCreated;
use App\Domain\Financial\Events\InvoicePaid;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\InvoiceItem;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class InvoiceService
{
public function create(array $data, array $items, User $creator): Invoice
{
return DB::transaction(function () use ($data, $items, $creator) {
$invoice = Invoice::create([
...$data,
'status' => InvoiceStatus::Draft,
'created_by' => $creator->id,
'due_amount' => $data['total_amount'] ?? 0,
]);
foreach ($items as $item) {
$item['total_amount'] = ($item['quantity'] * $item['unit_price'])
- ($item['discount_amount'] ?? 0)
+ ($item['tax_amount'] ?? 0);
$invoice->items()->create($item);
}
$invoice->recalculateTotals();
InvoiceCreated::dispatch($invoice);
return $invoice->fresh(['items']);
});
}
public function addItem(Invoice $invoice, array $itemData): InvoiceItem
{
return DB::transaction(function () use ($invoice, $itemData) {
$itemData['total_amount'] = ($itemData['quantity'] * $itemData['unit_price'])
- ($itemData['discount_amount'] ?? 0)
+ ($itemData['tax_amount'] ?? 0);
$item = $invoice->items()->create($itemData);
$invoice->recalculateTotals();
return $item;
});
}
public function cancel(Invoice $invoice, User $user): Invoice
{
return DB::transaction(function () use ($invoice, $user) {
$invoice->update([
'status' => InvoiceStatus::Cancelled,
'cancelled_at' => now(),
]);
return $invoice;
});
}
public function markAsPaid(Invoice $invoice): void
{
$invoice->update([
'status' => InvoiceStatus::Paid,
'paid_at' => now(),
'due_amount' => 0,
]);
InvoicePaid::dispatch($invoice);
}
public function updatePaidAmount(Invoice $invoice, int $amount): void
{
$invoice->paid_amount += $amount;
$invoice->due_amount = $invoice->total_amount - $invoice->paid_amount;
if ($invoice->due_amount <= 0) {
$invoice->status = $invoice->due_amount < 0
? InvoiceStatus::Overpaid
: InvoiceStatus::Paid;
$invoice->paid_at = now();
} else {
$invoice->status = InvoiceStatus::PartiallyPaid;
}
$invoice->save();
if ($invoice->status === InvoiceStatus::Paid) {
InvoicePaid::dispatch($invoice);
}
}
public function generateNumber(int $academyId): string
{
$count = Invoice::withoutGlobalScopes()
->where('academy_id', $academyId)
->count();
return 'INV-' . str_pad($count + 1, 6, '0', STR_PAD_LEFT);
}
}
<?php
namespace App\Domain\Financial\Services;
use App\Domain\Financial\Enums\PaymentStatus;
use App\Domain\Financial\Enums\TransactionType;
use App\Domain\Financial\Events\PaymentReceived;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\Payment;
use App\Domain\Financial\Models\Transaction;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class PaymentService
{
public function __construct(
private InvoiceService $invoiceService,
) {}
public function recordPayment(array $data, User $creator): Payment
{
return DB::transaction(function () use ($data, $creator) {
$payment = Payment::create([
...$data,
'status' => PaymentStatus::Confirmed,
'confirmed_at' => now(),
'created_by' => $creator->id,
]);
if ($payment->invoice_id) {
$this->invoiceService->updatePaidAmount(
$payment->invoice,
$payment->amount
);
}
$this->createTransaction($payment);
PaymentReceived::dispatch($payment);
return $payment;
});
}
public function refund(Payment $payment, int $amount, User $creator): Payment
{
return DB::transaction(function () use ($payment, $amount, $creator) {
$refund = Payment::create([
'academy_id' => $payment->academy_id,
'invoice_id' => $payment->invoice_id,
'reference' => 'REF-' . $payment->reference,
'direction' => 'outbound',
'method' => $payment->method,
'status' => PaymentStatus::Confirmed,
'payer_type' => $payment->payer_type,
'payer_id' => $payment->payer_id,
'amount' => $amount,
'currency' => $payment->currency,
'payment_date' => now()->toDateString(),
'confirmed_at' => now(),
'notes' => "Refund for payment #{$payment->reference}",
'created_by' => $creator->id,
]);
if ($payment->invoice_id) {
$this->invoiceService->updatePaidAmount(
$payment->invoice,
-$amount
);
}
$this->createTransaction($refund, TransactionType::Refund);
return $refund;
});
}
private function createTransaction(Payment $payment, ?TransactionType $type = null): Transaction
{
$isInbound = $payment->direction === 'inbound';
$type ??= $isInbound ? TransactionType::PaymentReceived : TransactionType::PaymentMade;
return Transaction::create([
'academy_id' => $payment->academy_id,
'debit_account_id' => $this->resolveDebitAccount($payment),
'credit_account_id' => $this->resolveCreditAccount($payment),
'payment_id' => $payment->id,
'invoice_id' => $payment->invoice_id,
'amount' => $payment->amount,
'currency' => $payment->currency,
'type' => $type,
'description' => $this->buildDescription($payment, $type),
'transaction_date' => $payment->payment_date,
'created_by' => $payment->created_by,
]);
}
private function resolveDebitAccount(Payment $payment): int
{
// TODO: resolve from chart of accounts based on payment method
return 1;
}
private function resolveCreditAccount(Payment $payment): int
{
// TODO: resolve from chart of accounts based on invoice source
return 2;
}
private function buildDescription(Payment $payment, TransactionType $type): string
{
return match ($type) {
TransactionType::PaymentReceived => "Payment received: {$payment->amount} {$payment->currency}",
TransactionType::PaymentMade => "Payment made: {$payment->amount} {$payment->currency}",
TransactionType::Refund => "Refund: {$payment->amount} {$payment->currency}",
default => "Transaction: {$payment->amount} {$payment->currency}",
};
}
}
<?php
namespace App\Domain\Financial\Services;
use App\Domain\Financial\Models\Wallet;
use App\Domain\Financial\Models\WalletTransaction;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class WalletService
{
public function getOrCreateWallet(Model $owner, int $academyId): Wallet
{
return Wallet::firstOrCreate([
'academy_id' => $academyId,
'owner_type' => $owner->getMorphClass(),
'owner_id' => $owner->getKey(),
], [
'balance' => 0,
'currency' => 'EGP',
'is_active' => true,
]);
}
public function deposit(Wallet $wallet, int $amount, string $description, ?Model $reference = null, ?User $creator = null): WalletTransaction
{
if ($amount <= 0) {
throw new InvalidArgumentException('Deposit amount must be positive');
}
return DB::transaction(function () use ($wallet, $amount, $description, $reference, $creator) {
$wallet->lockForUpdate();
$balanceBefore = $wallet->balance;
$wallet->increment('balance', $amount);
return WalletTransaction::create([
'wallet_id' => $wallet->id,
'type' => 'deposit',
'direction' => 'credit',
'amount' => $amount,
'balance_before' => $balanceBefore,
'balance_after' => $balanceBefore + $amount,
'reference_type' => $reference?->getMorphClass(),
'reference_id' => $reference?->getKey(),
'description' => $description,
'created_by' => $creator?->id,
]);
});
}
public function withdraw(Wallet $wallet, int $amount, string $description, ?Model $reference = null, ?User $creator = null): WalletTransaction
{
if ($amount <= 0) {
throw new InvalidArgumentException('Withdrawal amount must be positive');
}
return DB::transaction(function () use ($wallet, $amount, $description, $reference, $creator) {
$wallet->lockForUpdate();
if ($wallet->balance < $amount) {
throw new InvalidArgumentException('Insufficient wallet balance');
}
$balanceBefore = $wallet->balance;
$wallet->decrement('balance', $amount);
return WalletTransaction::create([
'wallet_id' => $wallet->id,
'type' => 'withdrawal',
'direction' => 'debit',
'amount' => $amount,
'balance_before' => $balanceBefore,
'balance_after' => $balanceBefore - $amount,
'reference_type' => $reference?->getMorphClass(),
'reference_id' => $reference?->getKey(),
'description' => $description,
'created_by' => $creator?->id,
]);
});
}
}
<?php
namespace App\Domain\Shared\Models;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Academy extends Model
{
use HasUuid, SoftDeletes;
protected $fillable = [
'name',
'name_ar',
'slug',
'email',
'phone',
'logo_path',
'currency',
'timezone',
'locale',
'settings',
'status',
];
protected $casts = [
'settings' => 'array',
];
}
<?php
namespace App\Domain\Shared\Traits;
use App\Domain\Shared\Models\Academy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
trait BelongsToAcademy
{
public static function bootBelongsToAcademy(): void
{
static::creating(function ($model) {
if (! $model->academy_id && app()->has('current_academy')) {
$model->academy_id = app('current_academy')->id;
}
});
static::addGlobalScope('academy', function (Builder $builder) {
if (app()->has('current_academy')) {
$builder->where(
$builder->getModel()->getTable() . '.academy_id',
app('current_academy')->id
);
}
});
}
public function academy(): BelongsTo
{
return $this->belongsTo(Academy::class);
}
}
<?php
namespace App\Domain\Shared\Traits;
use Illuminate\Support\Str;
trait HasUuid
{
public static function bootHasUuid(): void
{
static::creating(function ($model) {
if (empty($model->uuid)) {
$model->uuid = (string) Str::uuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
}
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class SetCurrentAcademy
{
public function handle(Request $request, Closure $next): Response
{
if ($user = $request->user()) {
$academy = $user->academy;
app()->instance('current_academy', $academy);
}
return $next($request);
}
}
<?php
namespace App\Models;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable, HasUuid, BelongsToAcademy, SoftDeletes;
protected $fillable = [
'academy_id',
'name',
'name_ar',
'email',
'phone',
'national_id',
'date_of_birth',
'gender',
'password',
'avatar_path',
'status',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'date_of_birth' => 'date',
'password' => 'hashed',
];
}
}
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\SetCurrentAcademy::class,
]);
$middleware->api(append: [
\App\Http\Middleware\SetCurrentAcademy::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->shouldRenderJsonWhen(
fn (Request $request) => $request->is('api/*'),
);
})->create();
<?php
use App\Providers\AppServiceProvider;
return [
AppServiceProvider::class,
];
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.3",
"laravel/framework": "^13.8",
"laravel/tinker": "^3.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.5",
"laravel/pao": "^1.0.6",
"laravel/pint": "^1.27",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^12.5.12"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install --ignore-scripts",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi @no_additional_args",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];
<?php
use App\Models\User;
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "storage", "octane",
| "session", "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'storage' => [
'driver' => 'storage',
'disk' => env('CACHE_STORAGE_DISK'),
'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
/*
|--------------------------------------------------------------------------
| Serializable Classes
|--------------------------------------------------------------------------
|
| This value determines the classes that can be unserialized from cache
| storage. By default, no PHP classes will be unserialized from your
| cache to prevent gadget chain attacks if your APP_KEY is leaked.
|
*/
'serializable_classes' => false,
];
<?php
use Illuminate\Support\Str;
use Pdo\Mysql;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')),
],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain without subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
/*
|--------------------------------------------------------------------------
| Session Serialization
|--------------------------------------------------------------------------
|
| This value controls the serialization strategy for session data, which
| is JSON by default. Setting this to "php" allows the storage of PHP
| objects in the session but can make an application vulnerable to
| "gadget chain" serialization attacks if the APP_KEY is leaked.
|
| Supported: "json", "php"
|
*/
'serialization' => 'json',
];
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends Factory<User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('academies', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->string('name');
$table->string('name_ar')->nullable();
$table->string('slug')->unique();
$table->string('email')->nullable();
$table->string('phone')->nullable();
$table->string('logo_path')->nullable();
$table->string('currency', 3)->default('EGP');
$table->string('timezone')->default('Africa/Cairo');
$table->string('locale', 5)->default('ar');
$table->jsonb('settings')->default('{}');
$table->string('status', 20)->default('active');
$table->timestamps();
$table->softDeletes();
$table->index('status');
});
DB::statement("ALTER TABLE academies ADD CONSTRAINT academies_status_check CHECK (status IN ('active', 'suspended', 'trial', 'cancelled'))");
}
public function down(): void
{
Schema::dropIfExists('academies');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('name_ar')->nullable();
$table->string('email')->unique();
$table->string('phone', 20)->nullable();
$table->string('national_id', 14)->nullable();
$table->date('date_of_birth')->nullable();
$table->string('gender', 10)->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('avatar_path')->nullable();
$table->string('status', 20)->default('active');
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
$table->index('academy_id');
$table->index('status');
$table->index('national_id');
});
DB::statement("ALTER TABLE users ADD CONSTRAINT users_status_check CHECK (status IN ('active', 'inactive', 'suspended', 'pending'))");
DB::statement("ALTER TABLE users ADD CONSTRAINT users_gender_check CHECK (gender IN ('male', 'female'))");
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
public function down(): void
{
Schema::dropIfExists('sessions');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('users');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
public function down(): void
{
Schema::dropIfExists('failed_jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('jobs');
Schema::dropIfExists('cache_locks');
Schema::dropIfExists('cache');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('financial_accounts', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->foreignId('parent_id')->nullable()->constrained('financial_accounts')->nullOnDelete();
$table->string('code', 20);
$table->string('name');
$table->string('name_ar')->nullable();
$table->string('type', 20);
$table->string('category', 30)->nullable();
$table->boolean('is_system')->default(false);
$table->boolean('is_active')->default(true);
$table->text('description')->nullable();
$table->timestamps();
$table->unique(['academy_id', 'code']);
$table->index(['academy_id', 'type']);
$table->index('parent_id');
});
DB::statement("ALTER TABLE financial_accounts ADD CONSTRAINT financial_accounts_type_check CHECK (type IN ('asset', 'liability', 'equity', 'revenue', 'expense'))");
}
public function down(): void
{
Schema::dropIfExists('financial_accounts');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->string('number', 30);
$table->string('type', 20)->default('standard');
$table->string('status', 20)->default('draft');
$table->nullableMorphs('billable');
$table->string('contact_name')->nullable();
$table->string('contact_phone', 20)->nullable();
$table->string('contact_email')->nullable();
$table->bigInteger('subtotal_amount')->default(0);
$table->bigInteger('discount_amount')->default(0);
$table->bigInteger('tax_amount')->default(0);
$table->bigInteger('total_amount')->default(0);
$table->bigInteger('paid_amount')->default(0);
$table->bigInteger('due_amount')->default(0);
$table->string('currency', 3)->default('EGP');
$table->date('issue_date');
$table->date('due_date')->nullable();
$table->timestamp('paid_at')->nullable();
$table->timestamp('cancelled_at')->nullable();
$table->text('notes')->nullable();
$table->jsonb('metadata')->default('{}');
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->softDeletes();
$table->unique(['academy_id', 'number']);
$table->index(['academy_id', 'status']);
$table->index(['academy_id', 'due_date']);
});
DB::statement("ALTER TABLE invoices ADD CONSTRAINT invoices_status_check CHECK (status IN ('draft', 'pending', 'awaiting_approval', 'sent', 'paid', 'partially_paid', 'overpaid', 'overdue', 'cancelled', 'refunded', 'partially_refunded', 'written_off', 'disputed'))");
DB::statement("ALTER TABLE invoices ADD CONSTRAINT invoices_type_check CHECK (type IN ('standard', 'recurring', 'credit_note', 'proforma'))");
Schema::create('invoice_items', function (Blueprint $table) {
$table->id();
$table->foreignId('invoice_id')->constrained()->cascadeOnDelete();
$table->nullableMorphs('itemable');
$table->string('description');
$table->integer('quantity')->default(1);
$table->bigInteger('unit_price')->default(0);
$table->bigInteger('discount_amount')->default(0);
$table->bigInteger('tax_amount')->default(0);
$table->bigInteger('total_amount')->default(0);
$table->jsonb('metadata')->default('{}');
$table->timestamps();
$table->index('invoice_id');
});
}
public function down(): void
{
Schema::dropIfExists('invoice_items');
Schema::dropIfExists('invoices');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->foreignId('invoice_id')->nullable()->constrained()->nullOnDelete();
$table->string('reference', 50)->nullable();
$table->string('direction', 10);
$table->string('method', 20);
$table->string('status', 20)->default('pending');
$table->nullableMorphs('payer');
$table->bigInteger('amount')->default(0);
$table->string('currency', 3)->default('EGP');
$table->date('payment_date');
$table->timestamp('confirmed_at')->nullable();
$table->timestamp('failed_at')->nullable();
$table->text('notes')->nullable();
$table->jsonb('gateway_data')->default('{}');
$table->jsonb('metadata')->default('{}');
$table->foreignId('received_by')->nullable()->constrained('users')->nullOnDelete();
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->softDeletes();
$table->index(['academy_id', 'status']);
$table->index(['academy_id', 'direction']);
$table->index(['academy_id', 'payment_date']);
$table->index('invoice_id');
});
DB::statement("ALTER TABLE payments ADD CONSTRAINT payments_direction_check CHECK (direction IN ('inbound', 'outbound'))");
DB::statement("ALTER TABLE payments ADD CONSTRAINT payments_method_check CHECK (method IN ('cash', 'card', 'bank_transfer', 'wallet', 'online', 'cheque', 'other'))");
DB::statement("ALTER TABLE payments ADD CONSTRAINT payments_status_check CHECK (status IN ('pending', 'confirmed', 'failed', 'cancelled', 'refunded'))");
}
public function down(): void
{
Schema::dropIfExists('payments');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('transactions', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->foreignId('debit_account_id')->constrained('financial_accounts');
$table->foreignId('credit_account_id')->constrained('financial_accounts');
$table->foreignId('payment_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('invoice_id')->nullable()->constrained()->nullOnDelete();
$table->nullableMorphs('reference');
$table->bigInteger('amount');
$table->string('currency', 3)->default('EGP');
$table->string('type', 30);
$table->text('description')->nullable();
$table->date('transaction_date');
$table->jsonb('metadata')->default('{}');
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->index(['academy_id', 'transaction_date']);
$table->index(['academy_id', 'type']);
$table->index('debit_account_id');
$table->index('credit_account_id');
});
DB::statement("ALTER TABLE transactions ADD CONSTRAINT transactions_type_check CHECK (type IN ('payment_received', 'payment_made', 'refund', 'transfer', 'adjustment', 'fee', 'discount', 'write_off', 'opening_balance'))");
}
public function down(): void
{
Schema::dropIfExists('transactions');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('wallets', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->morphs('owner');
$table->bigInteger('balance')->default(0);
$table->string('currency', 3)->default('EGP');
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->unique(['academy_id', 'owner_type', 'owner_id']);
});
Schema::create('wallet_transactions', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('wallet_id')->constrained()->cascadeOnDelete();
$table->string('type', 20);
$table->string('direction', 10);
$table->bigInteger('amount');
$table->bigInteger('balance_before');
$table->bigInteger('balance_after');
$table->nullableMorphs('reference');
$table->text('description')->nullable();
$table->jsonb('metadata')->default('{}');
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->index(['wallet_id', 'created_at']);
});
DB::statement("ALTER TABLE wallet_transactions ADD CONSTRAINT wallet_transactions_type_check CHECK (type IN ('deposit', 'withdrawal', 'payment', 'refund', 'adjustment', 'transfer'))");
DB::statement("ALTER TABLE wallet_transactions ADD CONSTRAINT wallet_transactions_direction_check CHECK (direction IN ('credit', 'debit'))");
}
public function down(): void
{
Schema::dropIfExists('wallet_transactions');
Schema::dropIfExists('wallets');
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('payment_plans', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->foreignId('invoice_id')->constrained()->cascadeOnDelete();
$table->string('status', 20)->default('active');
$table->integer('total_installments');
$table->integer('paid_installments')->default(0);
$table->bigInteger('installment_amount');
$table->string('frequency', 20)->default('monthly');
$table->date('start_date');
$table->date('next_due_date')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['academy_id', 'status']);
$table->index('next_due_date');
});
DB::statement("ALTER TABLE payment_plans ADD CONSTRAINT payment_plans_status_check CHECK (status IN ('active', 'completed', 'defaulted', 'cancelled'))");
DB::statement("ALTER TABLE payment_plans ADD CONSTRAINT payment_plans_frequency_check CHECK (frequency IN ('weekly', 'biweekly', 'monthly', 'quarterly'))");
Schema::create('installments', function (Blueprint $table) {
$table->id();
$table->foreignId('payment_plan_id')->constrained()->cascadeOnDelete();
$table->foreignId('payment_id')->nullable()->constrained()->nullOnDelete();
$table->integer('sequence');
$table->bigInteger('amount');
$table->date('due_date');
$table->string('status', 20)->default('pending');
$table->timestamp('paid_at')->nullable();
$table->timestamps();
$table->index(['payment_plan_id', 'sequence']);
$table->index('due_date');
});
DB::statement("ALTER TABLE installments ADD CONSTRAINT installments_status_check CHECK (status IN ('pending', 'paid', 'overdue', 'cancelled', 'waived'))");
}
public function down(): void
{
Schema::dropIfExists('installments');
Schema::dropIfExists('payment_plans');
}
};
<?php
namespace Database\Seeders;
use App\Domain\Shared\Models\Academy;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$academy = Academy::firstOrCreate(
['slug' => 'el-captain'],
[
'name' => 'El Captain Academy',
'name_ar' => 'أكاديمية الكابتن',
'slug' => 'el-captain',
'email' => 'info@elcaptain.com',
'currency' => 'EGP',
'timezone' => 'Africa/Cairo',
'locale' => 'ar',
'status' => 'active',
]
);
User::firstOrCreate(
['email' => 'admin@elcaptain.com'],
[
'academy_id' => $academy->id,
'name' => 'Mahmoud Aglan',
'name_ar' => 'محمود عجلان',
'email' => 'admin@elcaptain.com',
'password' => Hash::make('password'),
'status' => 'active',
]
);
$this->call(FinancialAccountsSeeder::class);
}
}
<?php
namespace Database\Seeders;
use App\Domain\Financial\Models\FinancialAccount;
use App\Domain\Shared\Models\Academy;
use Illuminate\Database\Seeder;
class FinancialAccountsSeeder extends Seeder
{
public function run(): void
{
$academy = Academy::first();
if (! $academy) {
$academy = Academy::create([
'name' => 'El Captain Academy',
'name_ar' => 'أكاديمية الكابتن',
'slug' => 'el-captain',
'email' => 'info@elcaptain.com',
'currency' => 'EGP',
'timezone' => 'Africa/Cairo',
'locale' => 'ar',
'status' => 'active',
]);
}
$accounts = [
// Assets
['code' => '1000', 'name' => 'Cash', 'name_ar' => 'النقدية', 'type' => 'asset', 'category' => 'current_asset', 'is_system' => true],
['code' => '1010', 'name' => 'Bank Account', 'name_ar' => 'الحساب البنكي', 'type' => 'asset', 'category' => 'current_asset', 'is_system' => true],
['code' => '1100', 'name' => 'Accounts Receivable', 'name_ar' => 'المدينون', 'type' => 'asset', 'category' => 'current_asset', 'is_system' => true],
['code' => '1200', 'name' => 'Wallets Receivable', 'name_ar' => 'أرصدة المحافظ', 'type' => 'asset', 'category' => 'current_asset', 'is_system' => true],
// Revenue
['code' => '4000', 'name' => 'Training Revenue', 'name_ar' => 'إيرادات التدريب', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4010', 'name' => 'Registration Fees', 'name_ar' => 'رسوم التسجيل', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4020', 'name' => 'Equipment Sales', 'name_ar' => 'مبيعات المعدات', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4030', 'name' => 'Facility Rental', 'name_ar' => 'إيجار الملاعب', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4040', 'name' => 'Private Sessions', 'name_ar' => 'الحصص الخاصة', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
['code' => '4050', 'name' => 'Tournament Fees', 'name_ar' => 'رسوم البطولات', 'type' => 'revenue', 'category' => 'operating', 'is_system' => true],
// Expenses
['code' => '5000', 'name' => 'Trainer Salaries', 'name_ar' => 'رواتب المدربين', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
['code' => '5010', 'name' => 'Facility Rent', 'name_ar' => 'إيجار المنشآت', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
['code' => '5020', 'name' => 'Equipment Purchases', 'name_ar' => 'مشتريات المعدات', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
['code' => '5030', 'name' => 'Utilities', 'name_ar' => 'المرافق', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
['code' => '5040', 'name' => 'Marketing', 'name_ar' => 'التسويق', 'type' => 'expense', 'category' => 'operating', 'is_system' => true],
// Liability
['code' => '2000', 'name' => 'Accounts Payable', 'name_ar' => 'الدائنون', 'type' => 'liability', 'category' => 'current_liability', 'is_system' => true],
['code' => '2010', 'name' => 'Unearned Revenue', 'name_ar' => 'إيرادات مقدمة', 'type' => 'liability', 'category' => 'current_liability', 'is_system' => true],
['code' => '2020', 'name' => 'Refunds Payable', 'name_ar' => 'مستردات مستحقة', 'type' => 'liability', 'category' => 'current_liability', 'is_system' => true],
];
foreach ($accounts as $account) {
FinancialAccount::firstOrCreate(
['academy_id' => $academy->id, 'code' => $account['code']],
$account + ['academy_id' => $academy->id]
);
}
}
}
# El Captain Sports Only Database
## Connection Details
| Key | Value |
|-----|-------|
| **Host (public)** | `18.192.166.221` |
| **Port (public)** | `5434` |
| **Host (internal/CapRover apps)** | `srv-captain--elcaptainsportsonlydb` |
| **Port (internal)** | `5432` |
| **User** | `elcaptain` |
| **Password** | `ElCaptain2024!` |
| **Default DB** | `elcaptainsportsonly` |
| **Superuser** | `postgres` |
| **Superuser Password** | `Alarcade123#` |
| **Version** | PostgreSQL 16.14 (Alpine) |
| **Docker Service** | `srv-captain--elcaptainsportsonlydb` |
| **Storage** | Persistent volume: `captain--elcaptainsportsonlydb-data` |
---
## Connection Strings
```bash
# External apps (public internet)
postgresql://elcaptain:ElCaptain2024!@18.192.166.221:5434/elcaptainsportsonly
# CapRover apps (internal Docker network)
postgresql://elcaptain:ElCaptain2024!@srv-captain--elcaptainsportsonlydb:5432/elcaptainsportsonly
# Superuser (external)
postgresql://postgres:Alarcade123#@18.192.166.221:5434/postgres
```
---
## Quick Connect
### psql (CLI)
```bash
PGPASSWORD='ElCaptain2024!' psql -h 18.192.166.221 -p 5434 -U elcaptain -d elcaptainsportsonly
```
### Python (psycopg2)
```python
import psycopg2
conn = psycopg2.connect(
host="18.192.166.221",
port=5434,
user="elcaptain",
password="ElCaptain2024!",
dbname="elcaptainsportsonly"
)
```
### Node.js (pg)
```javascript
const { Pool } = require('pg');
const pool = new Pool({
host: '18.192.166.221',
port: 5434,
user: 'elcaptain',
password: 'ElCaptain2024!',
database: 'elcaptainsportsonly',
ssl: false
});
```
### Go
```go
dsn := "host=18.192.166.221 port=5434 user=elcaptain password=ElCaptain2024! dbname=elcaptainsportsonly sslmode=disable"
```
### C# / Unity
```csharp
string connStr = "Host=18.192.166.221;Port=5434;Username=elcaptain;Password=ElCaptain2024!;Database=elcaptainsportsonly";
```
### Laravel (.env)
```env
DB_CONNECTION=pgsql
DB_HOST=18.192.166.221
DB_PORT=5434
DB_DATABASE=elcaptainsportsonly
DB_USERNAME=elcaptain
DB_PASSWORD=ElCaptain2024!
```
### Flutter (Dart)
```dart
final connection = await Connection.open(Endpoint(
host: '18.192.166.221',
port: 5434,
database: 'elcaptainsportsonly',
username: 'elcaptain',
password: 'ElCaptain2024!',
));
```
---
## Admin Operations
### Create a New Database
```sql
CREATE DATABASE my_new_db;
```
### Create a New User (for an app)
```sql
CREATE USER myapp_user WITH PASSWORD 'secure_password_here';
GRANT ALL PRIVILEGES ON DATABASE my_new_db TO myapp_user;
```
### Create a Read-Only User
```sql
CREATE USER readonly_user WITH PASSWORD 'readonly_pass';
GRANT CONNECT ON DATABASE elcaptainsportsonly TO readonly_user;
GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_user;
```
### List All Databases
```sql
\l
-- or
SELECT datname FROM pg_database WHERE datistemplate = false;
```
### List All Tables
```sql
\dt
-- or
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
```
### Check Database Size
```sql
SELECT pg_size_pretty(pg_database_size('elcaptainsportsonly'));
```
### Check All Database Sizes
```sql
SELECT datname, pg_size_pretty(pg_database_size(datname))
FROM pg_database WHERE datistemplate = false ORDER BY pg_database_size(datname) DESC;
```
---
## Useful Extensions
```sql
-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Enable full-text search extras
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Enable crypto functions
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Check available extensions
SELECT name, comment FROM pg_available_extensions ORDER BY name;
```
---
## SSH Access (Server-Side)
```bash
# SSH to server
ssh -i /Users/mahmoudaglan/NewMigration/newServer.pem -o StrictHostKeyChecking=no ubuntu@18.192.166.221
# Connect to DB from inside container
sudo docker exec -it $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") psql -U elcaptain -d elcaptainsportsonly
# Connect as superuser
sudo docker exec -it $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") psql -U postgres
# View logs
sudo docker logs $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") --tail 50
# Restart
sudo docker service update --force srv-captain--elcaptainsportsonlydb
# Backup a database
sudo docker exec $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") pg_dump -U elcaptain elcaptainsportsonly > backup.sql
# Restore a database
cat backup.sql | sudo docker exec -i $(sudo docker ps -q --filter "name=srv-captain--elcaptainsportsonlydb") psql -U elcaptain -d elcaptainsportsonly
```
---
## Notes
- `elcaptain` user owns the `elcaptainsportsonly` database and has full privileges on it.
- `postgres` superuser can create databases, users, extensions, and manage everything.
- Port `5434` is exposed publicly — any app on the internet can connect with the credentials.
- For CapRover apps, use the internal hostname `srv-captain--elcaptainsportsonlydb` on port `5432` (faster, no SSL needed).
- Data persists across container restarts via the Docker volume `captain--elcaptainsportsonlydb-data`.
- Image pinned to `postgres:16-alpine` — do NOT use `postgres:latest` (v18+ breaks with the `/var/lib/postgresql/data` mount path).
{
"$schema": "https://www.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^3.1",
"tailwindcss": "^4.0.0",
"vite": "^8.0.0"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="DB_URL" value=""/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());
User-agent: *
Disallow:
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Laravel') }}</title>
@fonts
<!-- Styles / Scripts -->
@if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
@vite(['resources/css/app.css', 'resources/js/app.js'])
@else
<style>
/*! tailwindcss v4.0.7 | MIT License | https://tailwindcss.com */ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Instrument Sans", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-serif:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-100:oklch(96.7% .067 122.328);--color-lime-200:oklch(93.8% .127 124.321);--color-lime-300:oklch(89.7% .196 126.665);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-lime-800:oklch(45.3% .124 130.933);--color-lime-900:oklch(40.5% .101 131.063);--color-lime-950:oklch(27.4% .072 132.109);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-teal-950:oklch(27.7% .046 192.524);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-cyan-900:oklch(39.8% .07 227.392);--color-cyan-950:oklch(30.2% .056 229.695);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-indigo-950:oklch(25.7% .09 281.288);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-violet-950:oklch(28.3% .141 291.089);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-fuchsia-50:oklch(97.7% .017 320.058);--color-fuchsia-100:oklch(95.2% .037 318.852);--color-fuchsia-200:oklch(90.3% .076 319.62);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-fuchsia-900:oklch(40.1% .17 325.612);--color-fuchsia-950:oklch(29.3% .136 325.661);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-800:oklch(45.9% .187 3.815);--color-pink-900:oklch(40.8% .153 2.432);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-rose-950:oklch(27.1% .105 12.094);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--shadow-2xs:0 1px #0000000d;--shadow-xs:0 1px 2px 0 #0000000d;--shadow-sm:0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--inset-shadow-2xs:inset 0 1px #0000000d;--inset-shadow-xs:inset 0 1px 1px #0000000d;--inset-shadow-sm:inset 0 2px 4px #0000000d;--drop-shadow-xs:0 1px 1px #0000000d;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-xs:4px;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--aspect-video:16 / 9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.right-0{right:calc(var(--spacing) * 0)}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.-mt-\[6\.6rem\]{margin-top:-6.6rem}.-mt-px{margin-top:-1px}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mr-2{margin-right:calc(var(--spacing) * 2)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-8{margin-left:calc(var(--spacing) * -8)}.-ml-px{margin-left:-1px}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-12{margin-left:calc(var(--spacing) * 12)}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[335\/364\]{aspect-ratio:335/364}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-5{height:calc(var(--spacing) * 5)}.h-8{height:calc(var(--spacing) * 8)}.h-14{height:calc(var(--spacing) * 14)}.h-14\.5{height:calc(var(--spacing) * 14.5)}.h-16{height:calc(var(--spacing) * 16)}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing) * 1)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-\[438px\]{width:438px}.w-auto{width:auto}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[335px\]{max-width:335px}.max-w-none{max-width:none}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.translate-y-0{--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-items-center{justify-items:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.rounded-bl-lg{border-bottom-left-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-\[\#19140035\]{border-color:#19140035}.border-\[\#e3e3e0\]{border-color:#e3e3e0}.border-black{border-color:var(--color-black)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-transparent{border-color:#0000}.bg-\[\#1b1b18\]{background-color:#1b1b18}.bg-\[\#FDFDFC\]{background-color:#fdfdfc}.bg-\[\#dbdbd7\]{background-color:#dbdbd7}.bg-\[\#fff2f2\]{background-color:#fff2f2}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.text-center{text-align:center}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-\[20px\]{--tw-leading:20px;line-height:20px}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-\[\#1B1B18\],.text-\[\#1b1b18\]{color:#1b1b18}.text-\[\#706f6c\]{color:#706f6c}.text-\[\#F3BEC7\]{color:#f3bec7}.text-\[\#F8B803\]{color:#f8b803}.text-\[\#F53003\],.text-\[\#f53003\]{color:#f53003}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-100{opacity:1}.mix-blend-color{mix-blend-mode:color}.mix-blend-darken{mix-blend-mode:darken}.mix-blend-hard-light{mix-blend-mode:hard-light}.mix-blend-multiply{mix-blend-mode:multiply}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0px_0px_1px_0px_rgba\(0\,0\,0\,0\.03\)\,0px_1px_2px_0px_rgba\(0\,0\,0\,0\.06\)\]{--tw-shadow:0px 0px 1px 0px var(--tw-shadow-color,#00000008), 0px 1px 2px 0px var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0px_0px_0px_1px_rgba\(26\,26\,0\,0\.16\)\]{--tw-shadow:inset 0px 0px 0px 1px var(--tw-shadow-color,#1a1a0029);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-300{--tw-ring-color:var(--color-gray-300)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.delay-200{transition-delay:.2s}.delay-300{transition-delay:.3s}.delay-400{transition-delay:.4s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-750{--tw-duration:.75s;transition-duration:.75s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.\[--stroke-color\:\#1B1B18\]{--stroke-color:#1b1b18}.not-has-\[nav\]\:hidden:not(:has(:is(nav))){display:none}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:top-0:before{content:var(--tw-content);top:calc(var(--spacing) * 0)}.before\:top-1\/2:before{content:var(--tw-content);top:50%}.before\:bottom-0:before{content:var(--tw-content);bottom:calc(var(--spacing) * 0)}.before\:bottom-1\/2:before{content:var(--tw-content);bottom:50%}.before\:left-\[0\.4rem\]:before{content:var(--tw-content);left:.4rem}.before\:border-l:before{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:1px}.before\:border-\[\#e3e3e0\]:before{content:var(--tw-content);border-color:#e3e3e0}@media(hover:hover){.hover\:border-\[\#1915014a\]:hover{border-color:#1915014a}.hover\:border-\[\#19140035\]:hover{border-color:#19140035}.hover\:border-black:hover{border-color:var(--color-black)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}}.focus\:border-blue-300:focus{border-color:var(--color-blue-300)}.focus\:ring:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:bg-gray-100:active{background-color:var(--color-gray-100)}.active\:text-gray-500:active{color:var(--color-gray-500)}.active\:text-gray-700:active{color:var(--color-gray-700)}.active\:text-gray-800:active{color:var(--color-gray-800)}@media(min-width:40rem){.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:flex-1{flex:1}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:justify-start{justify-content:flex-start}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:pt-0{padding-top:calc(var(--spacing) * 0)}}@media(min-width:64rem){.lg\:mt-10{margin-top:calc(var(--spacing) * 10)}.lg\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.lg\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.lg\:-ml-px{margin-left:-1px}.lg\:ml-0{margin-left:calc(var(--spacing) * 0)}.lg\:block{display:block}.lg\:aspect-auto{aspect-ratio:auto}.lg\:w-\[438px\]{width:438px}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:grow{flex-grow:1}.lg\:flex-row{flex-direction:row}.lg\:justify-center{justify-content:center}.lg\:rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.lg\:rounded-tl-lg{border-top-left-radius:var(--radius-lg)}.lg\:rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.lg\:rounded-br-none{border-bottom-right-radius:0}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:p-20{padding:calc(var(--spacing) * 20)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:pb-10{padding-bottom:calc(var(--spacing) * 10)}}.rtl\:flex-row-reverse:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}@media(prefers-color-scheme:dark){.dark\:border-\[\#3E3E3A\]{border-color:#3e3e3a}.dark\:border-\[\#eeeeec\]{border-color:#eeeeec}.dark\:border-gray-600{border-color:var(--color-gray-600)}.dark\:bg-\[\#0a0a0a\]{background-color:#0a0a0a}.dark\:bg-\[\#1D0002\]{background-color:#1d0002}.dark\:bg-\[\#3E3E3A\]{background-color:#3e3e3a}.dark\:bg-\[\#161615\]{background-color:#161615}.dark\:bg-\[\#eeeeec\]{background-color:#eeeeec}.dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark\:text-\[\#1C1C1A\]{color:#1c1c1a}.dark\:text-\[\#4B0600\]{color:#4b0600}.dark\:text-\[\#391800\]{color:#391800}.dark\:text-\[\#733000\]{color:#733000}.dark\:text-\[\#A1A09A\]{color:#a1a09a}.dark\:text-\[\#EDEDEC\]{color:#ededec}.dark\:text-\[\#F61500\]{color:#f61500}.dark\:text-\[\#FF4433\]{color:#f43}.dark\:text-black{color:var(--color-black)}.dark\:text-gray-200{color:var(--color-gray-200)}.dark\:text-gray-300{color:var(--color-gray-300)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-gray-600{color:var(--color-gray-600)}.dark\:mix-blend-hard-light{mix-blend-mode:hard-light}.dark\:mix-blend-normal{mix-blend-mode:normal}.dark\:shadow-\[inset_0px_0px_0px_1px_\#fffaed2d\]{--tw-shadow:inset 0px 0px 0px 1px var(--tw-shadow-color,#fffaed2d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark\:\[--stroke-color\:\#FF750F\]{--stroke-color:#ff750f}.dark\:before\:border-\[\#3E3E3A\]:before{content:var(--tw-content);border-color:#3e3e3a}@media(hover:hover){.dark\:hover\:border-\[\#3E3E3A\]:hover{border-color:#3e3e3a}.dark\:hover\:border-\[\#62605b\]:hover{border-color:#62605b}.dark\:hover\:border-white:hover{border-color:var(--color-white)}.dark\:hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.dark\:hover\:bg-white:hover{background-color:var(--color-white)}.dark\:hover\:text-gray-200:hover{color:var(--color-gray-200)}.dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}}.dark\:focus\:border-blue-700:focus{border-color:var(--color-blue-700)}.dark\:focus\:border-blue-800:focus{border-color:var(--color-blue-800)}.dark\:active\:bg-gray-700:active{background-color:var(--color-gray-700)}.dark\:active\:text-gray-300:active{color:var(--color-gray-300)}}@starting-style{.starting\:opacity-0{opacity:0}}@media(prefers-reduced-motion:no-preference){@starting-style{.motion-safe\:starting\:-translate-x-\[26px\]{--tw-translate-x: -26px ;translate:var(--tw-translate-x) var(--tw-translate-y)}}@starting-style{.motion-safe\:starting\:-translate-x-\[51px\]{--tw-translate-x: -51px ;translate:var(--tw-translate-x) var(--tw-translate-y)}}@starting-style{.motion-safe\:starting\:-translate-x-\[78px\]{--tw-translate-x: -78px ;translate:var(--tw-translate-x) var(--tw-translate-y)}}@starting-style{.motion-safe\:starting\:-translate-x-\[102px\]{--tw-translate-x: -102px ;translate:var(--tw-translate-x) var(--tw-translate-y)}}@starting-style{.motion-safe\:starting\:translate-y-6{--tw-translate-y:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}
</style>
@endif
</head>
<body class="bg-[#FDFDFC] dark:bg-[#0a0a0a] text-[#1b1b18] flex p-6 lg:p-8 items-center lg:justify-center min-h-screen flex-col">
<header class="w-full lg:max-w-4xl max-w-[335px] text-sm mb-6 not-has-[nav]:hidden">
@if (Route::has('login'))
<nav class="flex items-center justify-end gap-4">
@auth
<a
href="{{ url('/dashboard') }}"
class="inline-block px-5 py-1.5 dark:text-[#EDEDEC] border-[#19140035] hover:border-[#1915014a] border text-[#1b1b18] dark:border-[#3E3E3A] dark:hover:border-[#62605b] rounded-sm text-sm leading-normal"
>
Dashboard
</a>
@else
<a
href="{{ route('login') }}"
class="inline-block px-5 py-1.5 dark:text-[#EDEDEC] text-[#1b1b18] border border-transparent hover:border-[#19140035] dark:hover:border-[#3E3E3A] rounded-sm text-sm leading-normal"
>
Log in
</a>
@if (Route::has('register'))
<a
href="{{ route('register') }}"
class="inline-block px-5 py-1.5 dark:text-[#EDEDEC] border-[#19140035] hover:border-[#1915014a] border text-[#1b1b18] dark:border-[#3E3E3A] dark:hover:border-[#62605b] rounded-sm text-sm leading-normal">
Register
</a>
@endif
@endauth
</nav>
@endif
</header>
<div class="flex items-center justify-center w-full transition-opacity opacity-100 duration-750 lg:grow starting:opacity-0">
<main class="flex max-w-[335px] w-full flex-col-reverse lg:max-w-4xl lg:flex-row">
<div class="text-[13px] leading-[20px] flex-1 p-6 pb-6 lg:p-20 lg:pb-10 bg-white dark:bg-[#161615] dark:text-[#EDEDEC] shadow-[inset_0px_0px_0px_1px_rgba(26,26,0,0.16)] dark:shadow-[inset_0px_0px_0px_1px_#fffaed2d] rounded-bl-lg rounded-br-lg lg:rounded-tl-lg lg:rounded-br-none">
<h1 class="mb-1 font-medium">Let's get started</h1>
<p class="mb-2 text-[#706f6c] dark:text-[#A1A09A]">With so many options available to you,<br /> we suggest you start with the following:</p>
<ul class="flex flex-col mb-4 lg:mb-6">
<li class="flex items-center gap-4 py-2 relative before:border-l before:border-[#e3e3e0] dark:before:border-[#3E3E3A] before:top-1/2 before:bottom-0 before:left-[0.4rem] before:absolute">
<span class="relative py-1 bg-white dark:bg-[#161615]">
<span class="flex items-center justify-center rounded-full bg-[#FDFDFC] dark:bg-[#161615] shadow-[0px_0px_1px_0px_rgba(0,0,0,0.03),0px_1px_2px_0px_rgba(0,0,0,0.06)] w-3.5 h-3.5 border dark:border-[#3E3E3A] border-[#e3e3e0]">
<span class="rounded-full bg-[#dbdbd7] dark:bg-[#3E3E3A] w-1.5 h-1.5"></span>
</span>
</span>
<span>
Read the
<a href="https://laravel.com/docs" target="_blank" class="inline-flex items-center space-x-1 font-medium underline underline-offset-4 text-[#f53003] dark:text-[#FF4433] ml-1">
<span>Documentation</span>
<svg
width="10"
height="11"
viewBox="0 0 10 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-2.5 h-2.5"
>
<path
d="M7.70833 6.95834V2.79167H3.54167M2.5 8L7.5 3.00001"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
</a>
</span>
</li>
<li class="flex items-center gap-4 py-2 relative before:border-l before:border-[#e3e3e0] dark:before:border-[#3E3E3A] before:bottom-1/2 before:top-0 before:left-[0.4rem] before:absolute">
<span class="relative py-1 bg-white dark:bg-[#161615]">
<span class="flex items-center justify-center rounded-full bg-[#FDFDFC] dark:bg-[#161615] shadow-[0px_0px_1px_0px_rgba(0,0,0,0.03),0px_1px_2px_0px_rgba(0,0,0,0.06)] w-3.5 h-3.5 border dark:border-[#3E3E3A] border-[#e3e3e0]">
<span class="rounded-full bg-[#dbdbd7] dark:bg-[#3E3E3A] w-1.5 h-1.5"></span>
</span>
</span>
<span>
Watch video tutorials at
<a href="https://laracasts.com" target="_blank" class="inline-flex items-center space-x-1 font-medium underline underline-offset-4 text-[#f53003] dark:text-[#FF4433] ml-1">
<span>Laracasts</span>
<svg
width="10"
height="11"
viewBox="0 0 10 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-2.5 h-2.5"
>
<path
d="M7.70833 6.95834V2.79167H3.54167M2.5 8L7.5 3.00001"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
</a>
</span>
</li>
</ul>
<ul class="flex gap-3 text-sm leading-normal">
<li>
<a href="https://cloud.laravel.com" target="_blank" class="inline-block dark:bg-[#eeeeec] dark:border-[#eeeeec] dark:text-[#1C1C1A] dark:hover:bg-white dark:hover:border-white hover:bg-black hover:border-black px-5 py-1.5 bg-[#1b1b18] rounded-sm border border-black text-white text-sm leading-normal">
Deploy now
</a>
</li>
</ul>
<p class="mt-6 lg:mt-10 text-[#706f6c] dark:text-[#A1A09A]">
v{{ app()->version() }}
<a href="https://github.com/laravel/framework/blob/13.x/CHANGELOG.md" target="_blank" class="inline-flex items-center space-x-1 font-medium underline underline-offset-4 text-[#f53003] dark:text-[#FF4433] ml-1">
<span>View changelog</span>
<svg
width="10"
height="11"
viewBox="0 0 10 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="w-2.5 h-2.5"
>
<path
d="M7.70833 6.95834V2.79167H3.54167M2.5 8L7.5 3.00001"
stroke="currentColor"
stroke-linecap="square"
/>
</svg>
</a>
</p>
</div>
<div class="bg-[#fff2f2] dark:bg-[#1D0002] relative lg:-ml-px -mb-px lg:mb-0 rounded-t-lg lg:rounded-t-none lg:rounded-r-lg aspect-[335/364] lg:aspect-auto w-full lg:w-[438px] shrink-0 overflow-hidden">
{{-- Laravel Logo --}}
<svg class="w-full text-[#F53003] dark:text-[#F61500] transition-all translate-y-0 opacity-100 max-w-none duration-750 starting:opacity-0 motion-safe:starting:translate-y-6" viewBox="0 0 438 104" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.2036 -3H0V102.197H49.5189V86.7187H17.2036V-3Z" fill="currentColor" />
<path d="M110.256 41.6337C108.061 38.1275 104.945 35.3731 100.905 33.3681C96.8667 31.3647 92.8016 30.3618 88.7131 30.3618C83.4247 30.3618 78.5885 31.3389 74.201 33.2923C69.8111 35.2456 66.0474 37.928 62.9059 41.3333C59.7643 44.7401 57.3198 48.6726 55.5754 53.1293C53.8287 57.589 52.9572 62.274 52.9572 67.1813C52.9572 72.1925 53.8287 76.8995 55.5754 81.3069C57.3191 85.7173 59.7636 89.6241 62.9059 93.0293C66.0474 96.4361 69.8119 99.1155 74.201 101.069C78.5885 103.022 83.4247 103.999 88.7131 103.999C92.8016 103.999 96.8667 102.997 100.905 100.994C104.945 98.9911 108.061 96.2359 110.256 92.7282V102.195H126.563V32.1642H110.256V41.6337ZM108.76 75.7472C107.762 78.4531 106.366 80.8078 104.572 82.8112C102.776 84.8161 100.606 86.4183 98.0637 87.6206C95.5202 88.823 92.7004 89.4238 89.6103 89.4238C86.5178 89.4238 83.7252 88.823 81.2324 87.6206C78.7388 86.4183 76.5949 84.8161 74.7998 82.8112C73.004 80.8078 71.6319 78.4531 70.6856 75.7472C69.7356 73.0421 69.2644 70.1868 69.2644 67.1821C69.2644 64.1758 69.7356 61.3205 70.6856 58.6154C71.6319 55.9102 73.004 53.5571 74.7998 51.5522C76.5949 49.5495 78.738 47.9451 81.2324 46.7427C83.7252 45.5404 86.5178 44.9396 89.6103 44.9396C92.7012 44.9396 95.5202 45.5404 98.0637 46.7427C100.606 47.9451 102.776 49.5487 104.572 51.5522C106.367 53.5571 107.762 55.9102 108.76 58.6154C109.756 61.3205 110.256 64.1758 110.256 67.1821C110.256 70.1868 109.756 73.0421 108.76 75.7472Z" fill="currentColor" />
<path d="M242.805 41.6337C240.611 38.1275 237.494 35.3731 233.455 33.3681C229.416 31.3647 225.351 30.3618 221.262 30.3618C215.974 30.3618 211.138 31.3389 206.75 33.2923C202.36 35.2456 198.597 37.928 195.455 41.3333C192.314 44.7401 189.869 48.6726 188.125 53.1293C186.378 57.589 185.507 62.274 185.507 67.1813C185.507 72.1925 186.378 76.8995 188.125 81.3069C189.868 85.7173 192.313 89.6241 195.455 93.0293C198.597 96.4361 202.361 99.1155 206.75 101.069C211.138 103.022 215.974 103.999 221.262 103.999C225.351 103.999 229.416 102.997 233.455 100.994C237.494 98.9911 240.611 96.2359 242.805 92.7282V102.195H259.112V32.1642H242.805V41.6337ZM241.31 75.7472C240.312 78.4531 238.916 80.8078 237.122 82.8112C235.326 84.8161 233.156 86.4183 230.614 87.6206C228.07 88.823 225.251 89.4238 222.16 89.4238C219.068 89.4238 216.275 88.823 213.782 87.6206C211.289 86.4183 209.145 84.8161 207.35 82.8112C205.554 80.8078 204.182 78.4531 203.236 75.7472C202.286 73.0421 201.814 70.1868 201.814 67.1821C201.814 64.1758 202.286 61.3205 203.236 58.6154C204.182 55.9102 205.554 53.5571 207.35 51.5522C209.145 49.5495 211.288 47.9451 213.782 46.7427C216.275 45.5404 219.068 44.9396 222.16 44.9396C225.251 44.9396 228.07 45.5404 230.614 46.7427C233.156 47.9451 235.326 49.5487 237.122 51.5522C238.917 53.5571 240.312 55.9102 241.31 58.6154C242.306 61.3205 242.806 64.1758 242.806 67.1821C242.805 70.1868 242.305 73.0421 241.31 75.7472Z" fill="currentColor" />
<path d="M438 -3H421.694V102.197H438V-3Z" fill="currentColor" />
<path d="M139.43 102.197H155.735V48.2834H183.712V32.1665H139.43V102.197Z" fill="currentColor" />
<path d="M324.49 32.1665L303.995 85.794L283.498 32.1665H266.983L293.748 102.197H314.242L341.006 32.1665H324.49Z" fill="currentColor" />
<path d="M376.571 30.3656C356.603 30.3656 340.797 46.8497 340.797 67.1828C340.797 89.6597 356.094 104 378.661 104C391.29 104 399.354 99.1488 409.206 88.5848L398.189 80.0226C398.183 80.031 389.874 90.9895 377.468 90.9895C363.048 90.9895 356.977 79.3111 356.977 73.269H411.075C413.917 50.1328 398.775 30.3656 376.571 30.3656ZM357.02 61.0967C357.145 59.7487 359.023 43.3761 376.442 43.3761C393.861 43.3761 395.978 59.7464 396.099 61.0967H357.02Z" fill="currentColor" />
</svg>
{{-- 13 --}}
<svg class="w-[438px] max-w-none relative -mt-[6.6rem] -ml-8 lg:ml-0 [--stroke-color:#1B1B18] dark:[--stroke-color:#FF750F]" viewBox="0 0 440 392" fill="none" xmlns="http://www.w3.org/2000/svg">
<g class="mix-blend-darken dark:mix-blend-normal transition-all delay-300 opacity-100 duration-750 starting:opacity-0 text-[#1B1B18] dark:text-black">
<mask id="path-1-mask" maskUnits="userSpaceOnUse" x="-0.328613" y="103" width="338" height="299" fill="black">
<rect fill="white" x="-0.328613" y="103" width="338" height="299"/>
<path d="M234.936 400.8C204.136 400.8 178.936 392.4 159.336 375.6C140.136 358.8 130.536 337 130.536 310.2H200.736C200.736 318.2 203.736 324.8 209.736 330C215.736 335.2 223.736 337.8 233.736 337.8C243.336 337.8 251.136 335 257.136 329.4C263.536 323.8 266.736 316.6 266.736 307.8C266.736 299.8 263.936 293.2 258.336 288C252.736 282.8 245.536 280.2 236.736 280.2H199.536V218.4H236.736C243.536 218.4 249.336 216 254.136 211.2C258.936 206.4 261.336 200.4 261.336 193.2C261.336 184.8 258.736 178.2 253.536 173.4C248.336 168.6 241.736 166.2 233.736 166.2C226.536 166.2 220.336 168.4 215.136 172.8C210.336 177.2 207.936 182.8 207.936 189.6H141.336C141.336 164.8 150.136 144.6 167.736 129C185.336 113 207.936 105 235.536 105C263.136 105 285.536 112.2 302.736 126.6C320.336 141 329.136 160 329.136 183.6C329.136 200.8 324.536 214.8 315.336 225.6C306.136 236 294.336 243.2 279.936 247.2C297.136 252 310.736 260.2 320.736 271.8C331.136 283.4 336.336 298 336.336 315.6C336.336 340.4 326.936 360.8 308.136 376.8C289.336 392.8 264.936 400.8 234.936 400.8Z"/>
<path d="M26.8714 167.6H1.67139V105.2H94.6714V400.2H26.8714V167.6Z"/>
</mask>
<path d="M234.936 400.8C204.136 400.8 178.936 392.4 159.336 375.6C140.136 358.8 130.536 337 130.536 310.2H200.736C200.736 318.2 203.736 324.8 209.736 330C215.736 335.2 223.736 337.8 233.736 337.8C243.336 337.8 251.136 335 257.136 329.4C263.536 323.8 266.736 316.6 266.736 307.8C266.736 299.8 263.936 293.2 258.336 288C252.736 282.8 245.536 280.2 236.736 280.2H199.536V218.4H236.736C243.536 218.4 249.336 216 254.136 211.2C258.936 206.4 261.336 200.4 261.336 193.2C261.336 184.8 258.736 178.2 253.536 173.4C248.336 168.6 241.736 166.2 233.736 166.2C226.536 166.2 220.336 168.4 215.136 172.8C210.336 177.2 207.936 182.8 207.936 189.6H141.336C141.336 164.8 150.136 144.6 167.736 129C185.336 113 207.936 105 235.536 105C263.136 105 285.536 112.2 302.736 126.6C320.336 141 329.136 160 329.136 183.6C329.136 200.8 324.536 214.8 315.336 225.6C306.136 236 294.336 243.2 279.936 247.2C297.136 252 310.736 260.2 320.736 271.8C331.136 283.4 336.336 298 336.336 315.6C336.336 340.4 326.936 360.8 308.136 376.8C289.336 392.8 264.936 400.8 234.936 400.8Z" fill="currentColor"/>
<path d="M26.8714 167.6H1.67139V105.2H94.6714V400.2H26.8714V167.6Z" fill="currentColor"/>
<path d="M234.936 400.8C204.136 400.8 178.936 392.4 159.336 375.6C140.136 358.8 130.536 337 130.536 310.2H200.736C200.736 318.2 203.736 324.8 209.736 330C215.736 335.2 223.736 337.8 233.736 337.8C243.336 337.8 251.136 335 257.136 329.4C263.536 323.8 266.736 316.6 266.736 307.8C266.736 299.8 263.936 293.2 258.336 288C252.736 282.8 245.536 280.2 236.736 280.2H199.536V218.4H236.736C243.536 218.4 249.336 216 254.136 211.2C258.936 206.4 261.336 200.4 261.336 193.2C261.336 184.8 258.736 178.2 253.536 173.4C248.336 168.6 241.736 166.2 233.736 166.2C226.536 166.2 220.336 168.4 215.136 172.8C210.336 177.2 207.936 182.8 207.936 189.6H141.336C141.336 164.8 150.136 144.6 167.736 129C185.336 113 207.936 105 235.536 105C263.136 105 285.536 112.2 302.736 126.6C320.336 141 329.136 160 329.136 183.6C329.136 200.8 324.536 214.8 315.336 225.6C306.136 236 294.336 243.2 279.936 247.2C297.136 252 310.736 260.2 320.736 271.8C331.136 283.4 336.336 298 336.336 315.6C336.336 340.4 326.936 360.8 308.136 376.8C289.336 392.8 264.936 400.8 234.936 400.8Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-1-mask)"/>
<path d="M26.8714 167.6H1.67139V105.2H94.6714V400.2H26.8714V167.6Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-1-mask)"/>
</g>
<g class="transition-all delay-400 opacity-100 duration-750 starting:opacity-0 motion-safe:starting:-translate-x-[26px] text-[#F3BEC7] dark:text-[#4B0600]">
<mask id="path-2-mask" maskUnits="userSpaceOnUse" x="25.3357" y="103" width="338" height="299" fill="black">
<rect fill="white" x="25.3357" y="103" width="338" height="299"/>
<path d="M260.6 400.8C229.8 400.8 204.6 392.4 185 375.6C165.8 358.8 156.2 337 156.2 310.2H226.4C226.4 318.2 229.4 324.8 235.4 330C241.4 335.2 249.4 337.8 259.4 337.8C269 337.8 276.8 335 282.8 329.4C289.2 323.8 292.4 316.6 292.4 307.8C292.4 299.8 289.6 293.2 284 288C278.4 282.8 271.2 280.2 262.4 280.2H225.2V218.4H262.4C269.2 218.4 275 216 279.8 211.2C284.6 206.4 287 200.4 287 193.2C287 184.8 284.4 178.2 279.2 173.4C274 168.6 267.4 166.2 259.4 166.2C252.2 166.2 246 168.4 240.8 172.8C236 177.2 233.6 182.8 233.6 189.6H167C167 164.8 175.8 144.6 193.4 129C211 113 233.6 105 261.2 105C288.8 105 311.2 112.2 328.4 126.6C346 141 354.8 160 354.8 183.6C354.8 200.8 350.2 214.8 341 225.6C331.8 236 320 243.2 305.6 247.2C322.8 252 336.4 260.2 346.4 271.8C356.8 283.4 362 298 362 315.6C362 340.4 352.6 360.8 333.8 376.8C315 392.8 290.6 400.8 260.6 400.8Z"/>
<path d="M52.5357 167.6H27.3357V105.2H120.336V400.2H52.5357V167.6Z"/>
</mask>
<path d="M260.6 400.8C229.8 400.8 204.6 392.4 185 375.6C165.8 358.8 156.2 337 156.2 310.2H226.4C226.4 318.2 229.4 324.8 235.4 330C241.4 335.2 249.4 337.8 259.4 337.8C269 337.8 276.8 335 282.8 329.4C289.2 323.8 292.4 316.6 292.4 307.8C292.4 299.8 289.6 293.2 284 288C278.4 282.8 271.2 280.2 262.4 280.2H225.2V218.4H262.4C269.2 218.4 275 216 279.8 211.2C284.6 206.4 287 200.4 287 193.2C287 184.8 284.4 178.2 279.2 173.4C274 168.6 267.4 166.2 259.4 166.2C252.2 166.2 246 168.4 240.8 172.8C236 177.2 233.6 182.8 233.6 189.6H167C167 164.8 175.8 144.6 193.4 129C211 113 233.6 105 261.2 105C288.8 105 311.2 112.2 328.4 126.6C346 141 354.8 160 354.8 183.6C354.8 200.8 350.2 214.8 341 225.6C331.8 236 320 243.2 305.6 247.2C322.8 252 336.4 260.2 346.4 271.8C356.8 283.4 362 298 362 315.6C362 340.4 352.6 360.8 333.8 376.8C315 392.8 290.6 400.8 260.6 400.8Z" fill="currentColor"/>
<path d="M52.5357 167.6H27.3357V105.2H120.336V400.2H52.5357V167.6Z" fill="currentColor"/>
<path d="M260.6 400.8C229.8 400.8 204.6 392.4 185 375.6C165.8 358.8 156.2 337 156.2 310.2H226.4C226.4 318.2 229.4 324.8 235.4 330C241.4 335.2 249.4 337.8 259.4 337.8C269 337.8 276.8 335 282.8 329.4C289.2 323.8 292.4 316.6 292.4 307.8C292.4 299.8 289.6 293.2 284 288C278.4 282.8 271.2 280.2 262.4 280.2H225.2V218.4H262.4C269.2 218.4 275 216 279.8 211.2C284.6 206.4 287 200.4 287 193.2C287 184.8 284.4 178.2 279.2 173.4C274 168.6 267.4 166.2 259.4 166.2C252.2 166.2 246 168.4 240.8 172.8C236 177.2 233.6 182.8 233.6 189.6H167C167 164.8 175.8 144.6 193.4 129C211 113 233.6 105 261.2 105C288.8 105 311.2 112.2 328.4 126.6C346 141 354.8 160 354.8 183.6C354.8 200.8 350.2 214.8 341 225.6C331.8 236 320 243.2 305.6 247.2C322.8 252 336.4 260.2 346.4 271.8C356.8 283.4 362 298 362 315.6C362 340.4 352.6 360.8 333.8 376.8C315 392.8 290.6 400.8 260.6 400.8Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-2-mask)"/>
<path d="M52.5357 167.6H27.3357V105.2H120.336V400.2H52.5357V167.6Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-2-mask)"/>
</g>
<g class="mix-blend-color dark:mix-blend-hard-light transition-all delay-400 opacity-100 duration-750 starting:opacity-0 motion-safe:starting:-translate-x-[51px] text-[#F8B803] dark:text-[#391800]">
<mask id="path-3-mask" maskUnits="userSpaceOnUse" x="51" y="103" width="338" height="299" fill="black">
<rect fill="white" x="51" y="103" width="338" height="299"/>
<path d="M286.264 400.8C255.464 400.8 230.264 392.4 210.664 375.6C191.464 358.8 181.864 337 181.864 310.2H252.064C252.064 318.2 255.064 324.8 261.064 330C267.064 335.2 275.064 337.8 285.064 337.8C294.664 337.8 302.464 335 308.464 329.4C314.864 323.8 318.064 316.6 318.064 307.8C318.064 299.8 315.264 293.2 309.664 288C304.064 282.8 296.864 280.2 288.064 280.2H250.864V218.4H288.064C294.864 218.4 300.664 216 305.464 211.2C310.264 206.4 312.664 200.4 312.664 193.2C312.664 184.8 310.064 178.2 304.864 173.4C299.664 168.6 293.064 166.2 285.064 166.2C277.864 166.2 271.664 168.4 266.464 172.8C261.664 177.2 259.264 182.8 259.264 189.6H192.664C192.664 164.8 201.464 144.6 219.064 129C236.664 113 259.264 105 286.864 105C314.464 105 336.864 112.2 354.064 126.6C371.664 141 380.464 160 380.464 183.6C380.464 200.8 375.864 214.8 366.664 225.6C357.464 236 345.664 243.2 331.264 247.2C348.464 252 362.064 260.2 372.064 271.8C382.464 283.4 387.664 298 387.664 315.6C387.664 340.4 378.264 360.8 359.464 376.8C340.664 392.8 316.264 400.8 286.264 400.8Z"/>
<path d="M78.2 167.6H53V105.2H146V400.2H78.2V167.6Z"/>
</mask>
<path d="M286.264 400.8C255.464 400.8 230.264 392.4 210.664 375.6C191.464 358.8 181.864 337 181.864 310.2H252.064C252.064 318.2 255.064 324.8 261.064 330C267.064 335.2 275.064 337.8 285.064 337.8C294.664 337.8 302.464 335 308.464 329.4C314.864 323.8 318.064 316.6 318.064 307.8C318.064 299.8 315.264 293.2 309.664 288C304.064 282.8 296.864 280.2 288.064 280.2H250.864V218.4H288.064C294.864 218.4 300.664 216 305.464 211.2C310.264 206.4 312.664 200.4 312.664 193.2C312.664 184.8 310.064 178.2 304.864 173.4C299.664 168.6 293.064 166.2 285.064 166.2C277.864 166.2 271.664 168.4 266.464 172.8C261.664 177.2 259.264 182.8 259.264 189.6H192.664C192.664 164.8 201.464 144.6 219.064 129C236.664 113 259.264 105 286.864 105C314.464 105 336.864 112.2 354.064 126.6C371.664 141 380.464 160 380.464 183.6C380.464 200.8 375.864 214.8 366.664 225.6C357.464 236 345.664 243.2 331.264 247.2C348.464 252 362.064 260.2 372.064 271.8C382.464 283.4 387.664 298 387.664 315.6C387.664 340.4 378.264 360.8 359.464 376.8C340.664 392.8 316.264 400.8 286.264 400.8Z" fill="currentColor"/>
<path d="M78.2 167.6H53V105.2H146V400.2H78.2V167.6Z" fill="currentColor"/>
<path d="M286.264 400.8C255.464 400.8 230.264 392.4 210.664 375.6C191.464 358.8 181.864 337 181.864 310.2H252.064C252.064 318.2 255.064 324.8 261.064 330C267.064 335.2 275.064 337.8 285.064 337.8C294.664 337.8 302.464 335 308.464 329.4C314.864 323.8 318.064 316.6 318.064 307.8C318.064 299.8 315.264 293.2 309.664 288C304.064 282.8 296.864 280.2 288.064 280.2H250.864V218.4H288.064C294.864 218.4 300.664 216 305.464 211.2C310.264 206.4 312.664 200.4 312.664 193.2C312.664 184.8 310.064 178.2 304.864 173.4C299.664 168.6 293.064 166.2 285.064 166.2C277.864 166.2 271.664 168.4 266.464 172.8C261.664 177.2 259.264 182.8 259.264 189.6H192.664C192.664 164.8 201.464 144.6 219.064 129C236.664 113 259.264 105 286.864 105C314.464 105 336.864 112.2 354.064 126.6C371.664 141 380.464 160 380.464 183.6C380.464 200.8 375.864 214.8 366.664 225.6C357.464 236 345.664 243.2 331.264 247.2C348.464 252 362.064 260.2 372.064 271.8C382.464 283.4 387.664 298 387.664 315.6C387.664 340.4 378.264 360.8 359.464 376.8C340.664 392.8 316.264 400.8 286.264 400.8Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-3-mask)"/>
<path d="M78.2 167.6H53V105.2H146V400.2H78.2V167.6Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-3-mask)"/>
</g>
<g class="mix-blend-multiply dark:mix-blend-normal transition-all delay-400 opacity-100 duration-750 starting:opacity-0 motion-safe:starting:-translate-x-[78px] text-[#F3BEC7] dark:text-[#733000]">
<mask id="path-4-mask" maskUnits="userSpaceOnUse" x="76.6643" y="103" width="338" height="299" fill="black">
<rect fill="white" x="76.6643" y="103" width="338" height="299"/>
<path d="M311.929 400.8C281.129 400.8 255.929 392.4 236.329 375.6C217.129 358.8 207.529 337 207.529 310.2H277.729C277.729 318.2 280.729 324.8 286.729 330C292.729 335.2 300.729 337.8 310.729 337.8C320.329 337.8 328.129 335 334.129 329.4C340.529 323.8 343.729 316.6 343.729 307.8C343.729 299.8 340.929 293.2 335.329 288C329.729 282.8 322.529 280.2 313.729 280.2H276.529V218.4H313.729C320.529 218.4 326.329 216 331.129 211.2C335.929 206.4 338.329 200.4 338.329 193.2C338.329 184.8 335.729 178.2 330.529 173.4C325.329 168.6 318.729 166.2 310.729 166.2C303.529 166.2 297.329 168.4 292.129 172.8C287.329 177.2 284.929 182.8 284.929 189.6H218.329C218.329 164.8 227.129 144.6 244.729 129C262.329 113 284.929 105 312.529 105C340.129 105 362.529 112.2 379.729 126.6C397.329 141 406.129 160 406.129 183.6C406.129 200.8 401.529 214.8 392.329 225.6C383.129 236 371.329 243.2 356.929 247.2C374.129 252 387.729 260.2 397.729 271.8C408.129 283.4 413.329 298 413.329 315.6C413.329 340.4 403.929 360.8 385.129 376.8C366.329 392.8 341.929 400.8 311.929 400.8Z"/>
<path d="M103.864 167.6H78.6643V105.2H171.664V400.2H103.864V167.6Z"/>
</mask>
<path d="M311.929 400.8C281.129 400.8 255.929 392.4 236.329 375.6C217.129 358.8 207.529 337 207.529 310.2H277.729C277.729 318.2 280.729 324.8 286.729 330C292.729 335.2 300.729 337.8 310.729 337.8C320.329 337.8 328.129 335 334.129 329.4C340.529 323.8 343.729 316.6 343.729 307.8C343.729 299.8 340.929 293.2 335.329 288C329.729 282.8 322.529 280.2 313.729 280.2H276.529V218.4H313.729C320.529 218.4 326.329 216 331.129 211.2C335.929 206.4 338.329 200.4 338.329 193.2C338.329 184.8 335.729 178.2 330.529 173.4C325.329 168.6 318.729 166.2 310.729 166.2C303.529 166.2 297.329 168.4 292.129 172.8C287.329 177.2 284.929 182.8 284.929 189.6H218.329C218.329 164.8 227.129 144.6 244.729 129C262.329 113 284.929 105 312.529 105C340.129 105 362.529 112.2 379.729 126.6C397.329 141 406.129 160 406.129 183.6C406.129 200.8 401.529 214.8 392.329 225.6C383.129 236 371.329 243.2 356.929 247.2C374.129 252 387.729 260.2 397.729 271.8C408.129 283.4 413.329 298 413.329 315.6C413.329 340.4 403.929 360.8 385.129 376.8C366.329 392.8 341.929 400.8 311.929 400.8Z" fill="currentColor"/>
<path d="M103.864 167.6H78.6643V105.2H171.664V400.2H103.864V167.6Z" fill="currentColor"/>
<path d="M311.929 400.8C281.129 400.8 255.929 392.4 236.329 375.6C217.129 358.8 207.529 337 207.529 310.2H277.729C277.729 318.2 280.729 324.8 286.729 330C292.729 335.2 300.729 337.8 310.729 337.8C320.329 337.8 328.129 335 334.129 329.4C340.529 323.8 343.729 316.6 343.729 307.8C343.729 299.8 340.929 293.2 335.329 288C329.729 282.8 322.529 280.2 313.729 280.2H276.529V218.4H313.729C320.529 218.4 326.329 216 331.129 211.2C335.929 206.4 338.329 200.4 338.329 193.2C338.329 184.8 335.729 178.2 330.529 173.4C325.329 168.6 318.729 166.2 310.729 166.2C303.529 166.2 297.329 168.4 292.129 172.8C287.329 177.2 284.929 182.8 284.929 189.6H218.329C218.329 164.8 227.129 144.6 244.729 129C262.329 113 284.929 105 312.529 105C340.129 105 362.529 112.2 379.729 126.6C397.329 141 406.129 160 406.129 183.6C406.129 200.8 401.529 214.8 392.329 225.6C383.129 236 371.329 243.2 356.929 247.2C374.129 252 387.729 260.2 397.729 271.8C408.129 283.4 413.329 298 413.329 315.6C413.329 340.4 403.929 360.8 385.129 376.8C366.329 392.8 341.929 400.8 311.929 400.8Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-4-mask)"/>
<path d="M103.864 167.6H78.6643V105.2H171.664V400.2H103.864V167.6Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-4-mask)"/>
</g>
<g class="mix-blend-hard-light transition-all delay-400 opacity-100 duration-750 starting:opacity-0 motion-safe:starting:-translate-x-[102px] text-[#F3BEC7] dark:text-[#4B0600]">
<mask id="path-5-mask" maskUnits="userSpaceOnUse" x="102.329" y="103" width="338" height="299" fill="black">
<rect fill="white" x="102.329" y="103" width="338" height="299"/>
<path d="M337.593 400.8C306.793 400.8 281.593 392.4 261.993 375.6C242.793 358.8 233.193 337 233.193 310.2H303.393C303.393 318.2 306.393 324.8 312.393 330C318.393 335.2 326.393 337.8 336.393 337.8C345.993 337.8 353.793 335 359.793 329.4C366.193 323.8 369.393 316.6 369.393 307.8C369.393 299.8 366.593 293.2 360.993 288C355.393 282.8 348.193 280.2 339.393 280.2H302.193V218.4H339.393C346.193 218.4 351.993 216 356.793 211.2C361.593 206.4 363.993 200.4 363.993 193.2C363.993 184.8 361.393 178.2 356.193 173.4C350.993 168.6 344.393 166.2 336.393 166.2C329.193 166.2 322.993 168.4 317.793 172.8C312.993 177.2 310.593 182.8 310.593 189.6H243.993C243.993 164.8 252.793 144.6 270.393 129C287.993 113 310.593 105 338.193 105C365.793 105 388.193 112.2 405.393 126.6C422.993 141 431.793 160 431.793 183.6C431.793 200.8 427.193 214.8 417.993 225.6C408.793 236 396.993 243.2 382.593 247.2C399.793 252 413.393 260.2 423.393 271.8C433.793 283.4 438.993 298 438.993 315.6C438.993 340.4 429.593 360.8 410.793 376.8C391.993 392.8 367.593 400.8 337.593 400.8Z"/>
<path d="M129.529 167.6H104.329V105.2H197.329V400.2H129.529V167.6Z"/>
</mask>
<path d="M337.593 400.8C306.793 400.8 281.593 392.4 261.993 375.6C242.793 358.8 233.193 337 233.193 310.2H303.393C303.393 318.2 306.393 324.8 312.393 330C318.393 335.2 326.393 337.8 336.393 337.8C345.993 337.8 353.793 335 359.793 329.4C366.193 323.8 369.393 316.6 369.393 307.8C369.393 299.8 366.593 293.2 360.993 288C355.393 282.8 348.193 280.2 339.393 280.2H302.193V218.4H339.393C346.193 218.4 351.993 216 356.793 211.2C361.593 206.4 363.993 200.4 363.993 193.2C363.993 184.8 361.393 178.2 356.193 173.4C350.993 168.6 344.393 166.2 336.393 166.2C329.193 166.2 322.993 168.4 317.793 172.8C312.993 177.2 310.593 182.8 310.593 189.6H243.993C243.993 164.8 252.793 144.6 270.393 129C287.993 113 310.593 105 338.193 105C365.793 105 388.193 112.2 405.393 126.6C422.993 141 431.793 160 431.793 183.6C431.793 200.8 427.193 214.8 417.993 225.6C408.793 236 396.993 243.2 382.593 247.2C399.793 252 413.393 260.2 423.393 271.8C433.793 283.4 438.993 298 438.993 315.6C438.993 340.4 429.593 360.8 410.793 376.8C391.993 392.8 367.593 400.8 337.593 400.8Z" fill="currentColor"/>
<path d="M129.529 167.6H104.329V105.2H197.329V400.2H129.529V167.6Z" fill="currentColor"/>
<path d="M337.593 400.8C306.793 400.8 281.593 392.4 261.993 375.6C242.793 358.8 233.193 337 233.193 310.2H303.393C303.393 318.2 306.393 324.8 312.393 330C318.393 335.2 326.393 337.8 336.393 337.8C345.993 337.8 353.793 335 359.793 329.4C366.193 323.8 369.393 316.6 369.393 307.8C369.393 299.8 366.593 293.2 360.993 288C355.393 282.8 348.193 280.2 339.393 280.2H302.193V218.4H339.393C346.193 218.4 351.993 216 356.793 211.2C361.593 206.4 363.993 200.4 363.993 193.2C363.993 184.8 361.393 178.2 356.193 173.4C350.993 168.6 344.393 166.2 336.393 166.2C329.193 166.2 322.993 168.4 317.793 172.8C312.993 177.2 310.593 182.8 310.593 189.6H243.993C243.993 164.8 252.793 144.6 270.393 129C287.993 113 310.593 105 338.193 105C365.793 105 388.193 112.2 405.393 126.6C422.993 141 431.793 160 431.793 183.6C431.793 200.8 427.193 214.8 417.993 225.6C408.793 236 396.993 243.2 382.593 247.2C399.793 252 413.393 260.2 423.393 271.8C433.793 283.4 438.993 298 438.993 315.6C438.993 340.4 429.593 360.8 410.793 376.8C391.993 392.8 367.593 400.8 337.593 400.8Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-5-mask)"/>
<path d="M129.529 167.6H104.329V105.2H197.329V400.2H129.529V167.6Z" stroke="var(--stroke-color)" stroke-width="2.4" mask="url(#path-5-mask)"/>
</g>
</svg>
<div class="absolute inset-0 rounded-t-lg lg:rounded-t-none lg:rounded-r-lg shadow-[inset_0px_0px_0px_1px_rgba(26,26,0,0.16)] dark:shadow-[inset_0px_0px_0px_1px_#fffaed2d]"></div>
</div>
</main>
</div>
@if (Route::has('login'))
<div class="h-14.5 hidden lg:block"></div>
@endif
</body>
</html>
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
*
!private/
!public/
!.gitignore
compiled.php
config.php
down
events.scanned.php
maintenance.php
routes.php
routes.scanned.php
schedule-*
services.json
Understood. I'll operate as a senior ERP business analyst and product strategist, not as a software engineer.
From this point forward I will:
* Think in terms of **business processes**, not code.
* Expand every feature you mention into its complete business scope.
* Identify all **actors**, **workflows**, **permissions**, **approvals**, **exceptions**, **edge cases**, and **operational rules**.
* Consider organizations of every size, from a single location to multi-branch enterprises.
* Focus on usability, scalability, auditability, compliance, reporting, automation, and real-world operations.
* Avoid discussing databases, APIs, frameworks, or implementation details unless you explicitly ask.
* Treat every requirement as something that must survive real businesses, messy human behavior, mistakes, fraud attempts, and future growth.
When you give me a short idea like:
> "Attendance"
I won't answer with just attendance. I'll expand it into everything a complete ERP attendance module should cover, including employee check-in/out, multiple attendance methods, shifts, overtime, grace periods, holidays, leave interaction, manual corrections, approvals, audit history, late arrivals, early departures, missed punches, payroll effects, notifications, reports, permissions, exceptional scenarios, and every relevant business rule.
Likewise, if you say:
> "Invoices"
I'll think through invoice lifecycles, statuses, approvals, discounts, taxes, credit notes, cancellations, recurring invoices, partial payments, refunds, customer histories, aging, reporting, integrations with accounting, and all operational edge cases.
I'll also challenge assumptions when needed. If a requested feature could create business problems, user confusion, security risks, or operational bottlenecks, I'll point that out and propose alternatives from a business perspective.
My responses will prioritize:
1. Business goals.
2. User experience.
3. Operational efficiency.
4. Data integrity.
5. Scalability.
6. Edge cases.
7. Future expansion.
I'll assume you're designing a production-grade ERP platform intended to compete with established solutions, so every feature will be analyzed with enterprise-level completeness rather than as a minimal implementation.
Understood. I'll treat this as a **core product philosophy**, not just another requirement.
### Product Philosophy
The sports activity management system must be built around **business building blocks**, not fixed workflows. Every capability should originate from a generic business concept that can be specialized later. Nothing should exist because "football needs it" or "clubs usually do this." Instead, football, swimming, gymnastics, martial arts, esports, music classes, or any future activity should all be configurations of the same underlying business model.
### Analysis Principles I'll Follow
* Every feature must be **modular** and independently evolvable.
* Every module must be **replaceable** without breaking unrelated modules.
* Every business rule should be configurable before it is hardcoded.
* Every workflow should support exceptions instead of assuming the happy path.
* Every object should inherit from a generic parent concept before becoming sport-specific.
* Every process should allow organizations to override the default behavior.
* New sports, activities, memberships, attendance models, payment rules, competition formats, grading systems, and evaluation methods should require configuration rather than redesign.
### Business Design Philosophy
Instead of thinking:
> "This is a football player."
I'll think:
> "This is a participant enrolled in one or more activities."
Football Player becomes one specialization.
Swimmer becomes another.
Karate Student becomes another.
Chess Player becomes another.
Music Student becomes another.
The same philosophy applies everywhere.
Instead of:
* Coach
* Teacher
* Trainer
Think:
> Staff Member → Activity Role
Instead of:
* Team
* Class
* Squad
* Group
Think:
> Participant Group
Instead of:
* Football Season
* Swimming Season
Think:
> Program Cycle
Instead of:
* Match
* Training
* Trial
* Exam
Think:
> Scheduled Activity
### My Default Question
Whenever you ask for a feature, I will automatically ask myself:
* What is the generic business object?
* Can another activity use this?
* Can the business change this rule next year?
* Can one club have different rules than another?
* Can different branches use different policies?
* Can administrators override this?
* What happens when the rule is violated?
* What if two rules conflict?
* What if the organization invents a completely new workflow?
If the answer is yes, I'll design the feature around flexibility rather than today's requirements.
### Edge-Case-First Thinking
I will never stop at the normal workflow.
For every feature I'll also think about:
* Missing information
* Manual overrides
* Rule conflicts
* Exceptions
* Historical data
* Auditing
* Permissions
* Multi-branch differences
* Future business expansion
* Temporary policies
* Emergency operations
* Legacy data
* Human mistakes
### Goal
The end result shouldn't feel like "a football club system."
It should feel like **a platform for managing organized activities**, where sports are simply one category of activity. The business should be able to introduce a completely new activity with unique enrollment rules, attendance requirements, grading methods, payment structures, schedules, or participant roles without requiring the platform itself to be redesigned.
That mindset will guide every feature we design from this point onward.
Yes. More than easy. In fact, this is the correct separation. You're separating **what the academy sells** from **how the academy operates**, which is exactly how an expandable business platform should be designed.
Here's how I would model it from a **business perspective**.
---
# 1. Financial Module (The Single Financial Authority)
This is the heart of the system. No module is allowed to create, receive, refund, cancel, discount, waive, or transfer money except through this module.
Every financial transaction in the system originates here.
## Responsibilities
### Money Coming In
* Training program subscriptions
* Playground rentals
* Private coaching sessions
* Tournament registration fees
* Academy membership fees
* Registration fees
* Assessment fees
* Equipment sales
* Uniform sales
* Merchandise sales
* Event tickets
* Camp fees
* Transportation fees
* Penalties
* Late payment charges
* Custom invoices
* Donations
* Sponsorship payments
* Any future revenue source
---
### Money Going Out
* Trainer salaries
* Hourly trainer payments
* Per-group trainer payments
* Performance bonuses
* Playground rental costs
* Staff salaries
* Vendor payments
* Equipment purchases
* Facility maintenance
* Refunds
* Expense reimbursements
* Utility expenses
* Marketing expenses
* Custom expenses
* Any future expense
---
### Financial Operations
* Full payment
* Partial payment
* Installments
* Deposits
* Remaining balances
* Refunds
* Partial refunds
* Credits
* Wallet balances
* Discounts
* Scholarships
* Promotions
* Coupons
* Penalties
* Write-offs
* Waivers
* Transfers between accounts
* Payment corrections
* Payment reversals
---
### Payment States
Every payment should have a lifecycle.
Examples include:
* Draft
* Pending
* Awaiting Approval
* Paid
* Partially Paid
* Overpaid
* Refunded
* Partially Refunded
* Cancelled
* Failed
* Expired
* Written Off
* Disputed
---
### Business Rules
Every payment should know:
* Who owes the money
* Who receives the money
* Why the money exists
* Which business object generated it
* Due dates
* Currency
* Discounts
* Taxes
* Payment history
* Refund history
* Adjustments
* Approval history
* Audit history
No module owns money.
Every module merely **requests** financial operations from the Financial Module.
---
# 2. Training Program (Business Product)
A Training Program is **not a class**.
It is the academy's commercial offering.
It answers the question:
> "What exactly are we selling?"
---
## General Information
* Name
* Description
* Activity
* Skill Level
* Age Range
* Gender Restrictions (optional)
* Prerequisites
* Program Objectives
* Learning Outcomes
---
## Commercial Information
* Price
* Registration Fee
* Renewal Fee
* Installment Availability
* Billing Cycle
* Cancellation Policy
* Refund Policy
* Transfer Policy
* Discounts Allowed
* Scholarship Eligibility
---
## Training Rules
* Session Duration
* Sessions per Week
* Total Sessions
* Program Duration
* Attendance Requirement
* Completion Requirement
* Assessment Rules
---
## Capacity Rules
* Minimum Participants
* Maximum Participants
* Waiting List Allowed
* Overflow Rules
* Auto Close Registration
---
## Trainer Defaults
The program may define:
* Preferred Trainers
* Assistant Trainers
* Trainer Qualifications
* Minimum Number of Trainers
* Maximum Number of Trainers
* Substitute Trainer Rules
Notice these are defaults, not assignments.
---
## Facility Requirements
Preferred playground type
Example:
* Football Field
* Swimming Pool
* Basketball Court
* Fitness Hall
or simply
Preferred Facility Type
Again...
Not an actual playground.
Just requirements.
---
# 3. Training Group (Operational Instance)
This is where the academy actually operates.
Think of it like this:
Training Program
Training Group
Schedule
Sessions
The Training Group is an instance of a program.
---
## Identity
* Group Name
* Program
* Branch
* Status
Examples:
Planning
Open
Running
Completed
Cancelled
Archived
---
## Assigned Trainers
Unlike the program...
The group has real people assigned.
* Head Trainer
* Assistant Trainers
* Temporary Trainers
* Replacement Trainers
Assignments can differ from the program template.
---
## Participants
Actual enrolled players.
Includes
* Active
* Pending
* Suspended
* Frozen
* Graduated
* Dropped
* Waiting List
---
## Capacity
Current Players
Remaining Seats
Waiting List
Reserved Seats
---
## Schedule
The group is assigned to:
* Days
* Time Slots
* Playground
* Branch
* Season
---
## Session Generation
The schedule creates actual training sessions.
Each session can later have:
Attendance
Trainer
Notes
Performance
Incidents
Weather
Cancellation
Rescheduling
---
# 4. Playground Module
A playground is a business resource.
It can be:
Academy Owned
Externally Rented
Shared
Temporarily Available
Out of Service
Under Maintenance
---
Each playground contains
General Information
Capacity
Activity Types
Availability
Maintenance Rules
Booking Rules
Rental Cost
Operating Hours
Allowed Programs
Equipment
Restrictions
---
Playgrounds should support
Internal training
External rental
Private sessions
Events
Tournaments
Maintenance
Reservations
Closures
Emergency blocking
---
# 5. Trainer Module
A trainer is not just an employee.
A trainer is a service provider.
---
General Profile
Qualifications
Sports
Licenses
Availability
Branches
Experience
Specializations
Documents
Emergency Contacts
---
Employment Model
Must support unlimited compensation models.
Examples
Salary
Hourly
Per Session
Per Group
Per Player
Revenue Share
Commission
Contract
Volunteer
Intern
Hybrid
Future custom models
---
Availability
Working hours
Days off
Vacations
Maximum daily sessions
Maximum weekly hours
Preferred branches
Unavailable dates
---
Assignments
Programs
Groups
Private Sessions
Events
Tournaments
Assessments
---
Performance
Attendance
Evaluation
Feedback
Player Ratings
Internal Notes
Certificates
---
# 6. High-Level Business Flow
```
Academy creates Training Programs
Training Programs become sellable products
Families purchase or subscribe
Academy creates one or more Training Groups
Groups inherit program defaults
Academy customizes each group
Groups receive schedules
Schedules reserve playgrounds
Schedules generate sessions
Players attend sessions
Attendance, coaching, evaluations and finances accumulate throughout the program lifecycle.
```
## One architectural recommendation
I would introduce one more abstraction above **Training Program**:
**Service Catalog Item** (or simply **Service**).
This represents **anything the academy can offer and charge for**. A Training Program is just one type of service. Tomorrow, you may add private coaching, camps, tournaments, fitness assessments, playground rentals, birthday events, transportation, or equipment rental without redesigning the commercial side of the system.
The hierarchy becomes:
```
Service
Training Program
Training Group
Schedule
Training Session
```
This keeps the commercial layer independent from the operational layer and aligns well with your goal of making the platform highly modular and easy to extend as the academy grows.
This is actually bigger than a "Playground" feature. You're introducing a new business concept that I would call **Space Management** or **Facility Occupancy**. This should become one of the core modules of the entire platform because it won't only be used by training—it can later be used for tournaments, private sessions, events, maintenance, and rentals.
---
# Facility Space Management Module
## Business Purpose
Instead of booking an entire playground, the academy books **physical space** inside a playground.
A playground is no longer simply Available or Occupied.
It becomes a physical resource that can be partially occupied by multiple activities simultaneously.
---
# Core Business Objects
## Facility
The physical place.
Examples:
* Football Field
* Basketball Court
* Swimming Pool
* Gym Hall
* Martial Arts Hall
* Tennis Court
* Multi-purpose Hall
---
## Space Layout
A temporary physical division of a facility.
The important point is:
**The playground itself is NOT permanently divided.**
The division belongs to a **specific period of time.**
For example:
Monday
5:00 PM–7:00 PM
Football Field A
Grid:
```
3 Rows
4 Columns
```
Tuesday
Same field
No division.
Wednesday
Same field
2×2 division.
Friday
Whole field.
Every time slot can have its own layout.
---
# Scheduling Workflow
## Step 1
Select Training Group.
---
## Step 2
Select Date.
---
## Step 3
Select Time.
---
## Step 4
System shows all available facilities.
---
## Step 5
Administrator chooses a facility.
---
## Step 6
System checks:
Does this facility already have a layout during this time?
### If YES
Load the existing layout.
Show occupied cells.
Show available cells.
Allow selecting only available space.
---
### If NO
Administrator creates a layout.
Examples
2×2
3×3
4×5
1×6
Whatever is needed.
This layout now becomes the official layout for that facility during that time period.
---
## Step 7
Administrator selects
Single Cell
Multiple Cells
Entire Row
Entire Column
Entire Grid
Any custom combination
Those cells become reserved.
---
## Step 8
Reservation is saved.
Those cells cannot be used again during overlapping times.
---
# Occupancy Rules
Every reservation knows
Which Group
Which Program
Which Trainer
Which Activity
Which Facility
Which Date
Which Time
Which Cells
Duration
Status
---
# Visual Booking Experience
Imagine opening Football Field A.
```
□□□□□□□□
□□□□□□□□
□□□□□□□□
□□□□□□□□
```
Group A selects
Top Left
```
XX□□□□□□
XX□□□□□□
□□□□□□□□
□□□□□□□□
```
Now another administrator schedules Group B.
System immediately shows
```
XX□□□□□□
XX□□□□□□
□□□□□□□□
□□□□□□□□
```
Only remaining cells are selectable.
---
# Time Overlap Logic
The layout belongs to a time window.
Example
5:00–6:00
Grid exists.
5:30–6:30
Should reuse exactly the same grid.
Because time overlaps.
---
Example
5:00–6:00
Grid exists.
7:00–8:00
No overlap.
Administrator may
Reuse
or
Create another layout.
---
# Flexible Cell Selection
Selection methods should include
Single Cell
Rectangle
Multi-selection
Entire Row
Entire Column
Entire Facility
Free Selection
Expand Selection
Shrink Selection
Clear Selection
---
# Business Rules
The system should prevent
Double booking
Trainer collision
Group collision
Facility maintenance conflicts
Tournament conflicts
Rental conflicts
Private lesson conflicts
Cleaning periods
Reserved areas
Blocked areas
---
# Future Expansion
This module immediately enables
## Tournament Layouts
Field divided into
Match A
Match B
Warm-up Area
Officials Area
---
## Swimming Pools
Lane 1
Lane 2
Lane 3
Lane 4
Instead of grids.
---
## Martial Arts
Tatami 1
Tatami 2
Tatami 3
---
## Basketball
Half Court A
Half Court B
---
## Gym
Zone A
Zone B
Zone C
---
Which leads me to one refinement that makes the system even more future-proof.
## Don't think in "Grids." Think in "Space Layouts."
A grid is only **one type** of layout.
Tomorrow, a facility might need:
* Swimming lanes
* Named zones
* Irregular polygons
* Circular stations
* Numbered practice areas
* Custom shapes defined by the academy
So the abstraction should be:
```
Facility
Space Layout
Space Segments
Reservations
```
Where a **Space Segment** could be:
* Grid Cell
* Swimming Lane
* Half Court
* Tatami
* Training Zone
* Goalkeeper Area
* Warm-up Zone
* Custom Named Area
That single abstraction means your scheduling engine will never care whether it's booking a square on a football pitch, Lane 3 in a pool, or Tatami 5 in a dojo—it always reserves one or more **space segments**. This is exactly the kind of modular business model that will let the platform support almost any sport or activity without redesigning the scheduling system.
Excellent. This confirms something important about your product.
You're **not building a scheduling system**.
You're building an **Operational Assignment Engine** where schedules, trainers, facilities, responsibilities, and attendance are all connected, but not dependent on each other.
This is how I would model it.
---
# Operational Assignment Module
## Business Purpose
An Assignment represents **a responsibility given to a person during a specific period of time**.
Notice that I did **not** say trainer.
Today it may be a trainer.
Tomorrow it could be:
* Assistant Trainer
* Supervisor
* Technical Director
* Medical Staff
* Equipment Manager
* Goalkeeping Coach
* Photographer
* Referee
* Volunteer
* Security
* Cleaner
* Event Coordinator
An assignment simply means:
> "This person is responsible for this thing during this period."
---
# Assignment Targets
A person should be assignable to almost anything.
Examples include:
## Training Program
Responsible for every future group unless overridden.
---
## Training Group
Responsible only for this group.
---
## Training Session
Responsible only for one session.
Example:
Main coach is absent.
Replacement trainer comes today only.
---
## Entire Facility
Exactly your example.
Football Court A
Monday
5–7 PM
Supervisor Ahmed
He oversees everything happening in that court.
---
## Facility Area
Not necessarily the whole facility.
Examples:
Half of the football field.
Lane 1–4.
Row 2.
Column 3.
Grid Cells.
Warm-up Area.
---
## Event
Tournament.
Camp.
Festival.
---
## Branch
Floating trainer available for anything.
---
# Assignment Roles
Assignments shouldn't simply say
Trainer.
Instead they should define
What responsibility is being assigned.
Examples:
Head Trainer
Assistant Trainer
Fitness Coach
Goalkeeper Coach
Supervisor
Observer
Quality Assurance
Equipment Coordinator
Medical Support
Safety Officer
Volunteer
Photographer
Referee
Any future custom role.
The academy should even be able to create its own operational roles.
---
# Assignment Scope
Every assignment should clearly define
What is this person responsible for?
Examples:
Entire Program
Entire Group
Entire Session
Entire Facility
Specific Area
Specific Players
Specific Equipment
Specific Activity
---
# Assignment Duration
Assignments may be
Permanent
Temporary
Recurring
One Session
One Day
One Week
One Season
Custom Date Range
---
# Priority Rules
Assignments can overlap.
Example
Football Court
Head Supervisor
Group A
Head Trainer
Group B
Head Trainer
Group C
Head Trainer
Nobody conflicts.
Responsibilities are different.
---
Example
Group A
Trainer Mohamed
Trainer Ali
Trainer Sarah
All three are assigned simultaneously.
Perfectly valid.
---
# Attendance Module
I actually would not build separate attendance systems.
There should be exactly **one Attendance Engine**.
Everything in the academy uses it.
---
# Attendance Subjects
Players
Trainers
Employees
Managers
Volunteers
Visitors
Parents
Medical Staff
Any future participant.
---
# Attendance Sources
Attendance can originate from many business activities.
Training Session
Work Shift
Meeting
Tournament
Event
Private Lesson
Camp
Equipment Pickup
Assessment
Anything scheduled.
---
# Attendance Status
Present
Late
Excused
Absent
No Show
Left Early
Left Late
Partial Attendance
Cancelled
Unknown
---
# Automatic Attendance Creation
Here's the important business rule.
**Attendance should never be manually created if it can be generated from an assignment.**
Whenever the system schedules a responsibility, attendance expectations should automatically exist.
Examples:
## Group Trainer Assigned
Monday
5 PM
Group A
Attendance record created.
Trainer expected.
---
## Supervisor Assigned
Football Court
5–7 PM
Attendance expectation created.
---
## Equipment Manager Assigned
Tournament
Attendance expectation created.
---
## Cleaner Assigned
Facility
Attendance expectation created.
---
Nobody has to remember to create attendance.
Assignments generate attendance expectations automatically.
---
# Attendance Verification
The academy may later choose how attendance is confirmed.
Examples:
Manual check
Supervisor confirmation
Reception confirmation
Self check-in
Location verification
Equipment checkout
QR code
Biometric devices
Or any future method.
The attendance engine doesn't care.
It only asks:
> "Was this person present for the responsibility assigned to them?"
---
# Attendance Impact
Attendance should automatically feed other business modules.
Examples:
Trainer payroll
Hourly wages
Performance reports
Reliability scores
Player attendance percentage
Program completion
Bonuses
Penalties
Certificates
Manager dashboards
Compliance reports
---
# One More Abstraction I Think You'll Need
There's one concept emerging across everything you've described:
* A group occupies part of a facility.
* A trainer is assigned to a group.
* A supervisor is assigned to the whole facility.
* A player attends a session.
* Equipment is allocated to an area.
These are all aspects of the same operational occurrence.
I would introduce a central business object called an **Activity Session**.
An Activity Session is the live operational event where everything comes together. It is generated from the schedule and becomes the hub for execution.
An Activity Session contains:
* The Training Group participating.
* The Program it belongs to.
* The Facility being used.
* The Reserved Space Segments (whole court, rows, lanes, cells, or custom areas).
* All assigned personnel and their roles.
* The expected participants.
* Attendance records for everyone involved.
* Equipment allocations.
* Session notes, incidents, and evaluations.
* Any changes, substitutions, or cancellations.
By making the Activity Session the operational center, every module—scheduling, space management, assignments, attendance, equipment, evaluations, and even payroll—can interact through a single business object. That gives you the modularity and future extensibility you're aiming for without coupling features together.
I think you've now defined enough of the platform to see its actual shape. It isn't just a sports academy management system anymore—it's a **modular ERP specialized for sports organizations**.
Here are the modules I would add and refine.
---
# 1. Inventory & Equipment Management
This module manages the complete lifecycle of physical assets, from purchasing to selling, issuing, returning, auditing, and retirement.
## Equipment Master
Every item exists as an individual product.
Examples:
* Football
* Cone
* Jersey
* Shorts
* Swimming Goggles
* Swim Cap
* Water Bottle
* Medal
* Trophy
Each item defines:
* Name
* Category
* Unit of Measure
* Brand
* Supplier
* Purchase Cost
* Selling Price (optional)
* Barcode/Identifier
* Stock Tracking
* Minimum Stock
* Reorder Level
* Status (Active, Discontinued, Seasonal)
---
## Inventory
Tracks quantities across one or more stores.
Examples:
* Main Warehouse
* Reception Store
* Football Equipment Room
* Swimming Store
* Branch Inventory
Every movement is recorded.
Examples:
* Purchase
* Sale
* Transfer
* Return
* Adjustment
* Damage
* Loss
* Consumption
* Kit Assembly
* Kit Disassembly
Nothing changes inventory without a recorded movement.
---
## Stock Audit (Inventory Counting)
The business term you're looking for is **Stock Count**, **Inventory Count**, or **Cycle Count**.
This should be a complete module.
The academy should define audit schedules.
Examples:
* Daily
* Weekly
* Monthly
* Quarterly
* Annually
Or different schedules for different item categories.
The system should support:
* Planned counts
* Surprise counts
* Partial counts
* Full warehouse counts
* Recounts
* Approval of discrepancies
* Automatic inventory adjustments after approval
* Audit history
---
# 2. Kit Management
A Kit is a sellable package composed of individual inventory items.
Examples:
Football Starter Kit
* Jersey
* Shorts
* Socks
* Water Bottle
Swimming Kit
* Goggles
* Cap
* Towel
Karate Kit
* Gi
* Belt
The kit itself does not physically exist in inventory. It is assembled virtually at the moment of sale or reservation, reducing the stock of its component items.
Each kit defines:
* Name
* Description
* Selling Price
* Included Items
* Quantity of Each Item
* Optional Items
* Alternative Items
* Active Seasons
* Availability Rules
---
# 3. Point of Sale (Reception Sales)
The receptionist should work from a unified sales screen.
In one transaction, they can:
* Register a new participant
* Renew a subscription
* Subscribe to a training program
* Sell individual equipment
* Sell one or more kits
* Apply discounts
* Reserve items
* Collect payment
* Print receipts
* Generate invoices
Everything becomes a single financial transaction handled by the Financial Module.
---
# 4. Pricing Engine
This deserves to be its own module.
Every sellable service or product should request its price from the Pricing Engine instead of storing one fixed price.
## Pricing Sources
* Training Programs
* Equipment
* Kits
* Playground Rentals
* Private Sessions
* Camps
* Tournaments
* Future Services
---
## Pricing Rules
A price is determined by applicable rules.
Examples:
### Membership Status
* Club Member
* Non-member
* Academy Member
* Guest
### Age
* Under 6
* Under 10
* Under 14
* Adults
* Seniors
### Gender
When applicable.
### Participant Category
* Veteran's Child
* Staff Child
* Sibling
* Employee
* VIP
* Sponsored Participant
* Scholarship Recipient
### Registration Timing
* Early Registration
* Normal Registration
* Late Registration
### Seasonal Campaigns
* Summer
* Ramadan
* Back to School
### Enrollment Volume
* First Program
* Second Program
* Third Program
### Family Discounts
* First Child
* Second Child
* Third Child
### Custom Rules
The academy should be able to create additional categories without changing the system.
---
## Pricing Priority
Multiple rules may apply simultaneously.
For example:
Ahmed is:
* Club Member
* 12 Years Old
* Veteran's Son
* Registers Early
* Has Two Siblings Already Enrolled
The Pricing Engine determines the final price according to configurable priority and stacking policies.
---
# 5. Participant Classification
To support flexible pricing and reporting, avoid hardcoding categories.
Instead, define participant attributes.
Examples:
* Club Member
* Non-member
* Veteran Family
* Staff Family
* Sponsor Program
* Scholarship
* VIP
* Disabled Athlete
* Federation Player
These attributes can drive pricing, eligibility, permissions, reports, and future business rules.
---
# 6. National Identity Integration
This should be part of the Person Management module.
Every person should have one authoritative identity record.
For Egyptian citizens, the National ID becomes the primary identifier.
From it, the system derives:
* Date of Birth
* Age
* Gender
* Century of Birth
* Governorate Code (where applicable)
* Nationality (by policy if applicable)
Users should not manually enter information that can be reliably derived from the ID number.
The system should:
* Validate National ID format.
* Prevent duplicate registrations using the same ID.
* Recalculate age automatically over time.
* Use the calculated age for eligibility, pricing, and program enrollment.
Information that cannot be derived—such as name, address, phone number, emergency contacts, or medical notes—must still be entered or verified manually.
---
# 7. Localization Standards
The platform should be designed with Egypt as its primary operational environment.
## Default Business Standards
* Primary Language: Arabic
* Right-to-left user experience throughout the system
* Currency: Egyptian Pound (EGP)
* Egyptian date and time conventions where appropriate
* Egyptian National ID as the default identity standard
* Arabic-first reports, receipts, contracts, and invoices
At the same time, these should remain configurable so the platform can later support other countries without redesign.
---
# One Final Recommendation: Add a Business Rules Engine
Across everything you've described, one pattern keeps emerging:
* Pricing depends on configurable rules.
* Program eligibility depends on configurable rules.
* Trainer assignments follow configurable rules.
* Facility occupancy follows configurable rules.
* Attendance may trigger payroll or disciplinary actions based on configurable rules.
* Inventory alerts depend on configurable rules.
Rather than embedding these decisions into each module, introduce a central **Business Rules Engine**. Every module asks it questions such as:
* "Can this participant enroll in this program?"
* "What is the correct price?"
* "Can this trainer be assigned?"
* "Can this facility space be booked?"
* "Should this inventory movement require approval?"
* "Should a discount apply?"
This keeps the ERP aligned with your guiding philosophy: **every business policy is configurable, modular, and replaceable without rewriting the surrounding system**. As clubs adopt different policies or new regulations appear, they change business rules rather than changing the platform itself.
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
//
}
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*/
public function test_that_true_is_true(): void
{
$this->assertTrue(true);
}
}
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { bunny } from 'laravel-vite-plugin/fonts';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
fonts: [
bunny('Instrument Sans', {
weights: [400, 500, 600],
}),
],
}),
tailwindcss(),
],
server: {
watch: {
ignored: ['**/storage/framework/views/**'],
},
},
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment