🆙 Final fix delete storage link to fix news_images and logs 🆙

This commit is contained in:
Remco
2026-01-07 20:29:24 +01:00
parent 65ea6c167f
commit acf2d7e661
447 changed files with 208 additions and 66965 deletions
-18
View File
@@ -1,18 +0,0 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
-128
View File
@@ -1,128 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
# Change those to match your database settings
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=atomcms
DB_USERNAME=root
DB_PASSWORD=password
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=database
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=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
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
## Custom variables
RCON_HOST=127.0.0.1
RCON_PORT=3001
# Find retros settings
FINDRETROS_NAME=
FINDRETROS_ENABLED=false
# Recaptcha settings
GOOGLE_RECAPTCHA_SITE_KEY=
GOOGLE_RECAPTCHA_SECRET_KEY=
# Turnstile
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# If set to true, Atom will rename any colliding table names when migration the first time around
RENAME_COLLIDING_TABLES=false
# Flash client settings
FLASH_CLIENT_ENABLED=false
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
SWF_BASE_PATH=client/flash
HABBO_SWF=Habbo.swf
PRODUCTION_FOLDER=gordon/PRODUCTION
EXTERNAL_FURNIDATA=gamedata/furnidata.xml
EXTERNAL_FIGUREMAP=gamedata/figuremap.xml
EXTERNAL_FIGUREDATA=gamedata/figuredata.xml
EXTERNAL_PRODUCTDATA=gamedata/productdata.txt
EXTERNAL_TEXTS=gamedata/external_flash_texts.txt
EXTERNAL_VARIABLES=gamedata/external_variables.txt
EXTERNAL_OVERRIDE_TEXTS=gamedata/override/external_flash_override_texts.txt
EXTERNAL_OVERRIDE_VARIABLES=gamedata/override/external_override_variables.txt
# Only enable if you come from a CMS like RevCMS
# This will only work if your password is hashed using md5
# By default Atom CMS uses bcrypt, this is purely used to ease the process, when switching from a CMS using md5
CONVERT_PASSWORDS=false
# Enable this if your site is running through https, but you're experiencing issues with requests being made to "http"
FORCE_HTTPS=false
# Default language for the site
APP_LOCALE=en
# Enter the time in minutes that a password reset token can be valid
PASSWORD_RESET_TOKEN_TIME=15
# General paypal options
PAYPAL_MODE='sandbox'
PAYPAL_PAYMENT_ACTION='Order'
PAYPAL_CURRENCY='USD'
PAYPAL_NOTIFY_URL=
PAYPAL_LOCALE='en_US'
PAYPAL_VALIDATE_SSL=true
#PayPal Setting & API Credentials - sandbox
PAYPAL_SANDBOX_CLIENT_ID=
PAYPAL_SANDBOX_CLIENT_SECRET=
PAYPAL_SANDBOX_APP_ID=
#PayPal Setting & API Credentials - live
PAYPAL_LIVE_CLIENT_ID=
PAYPAL_LIVE_CLIENT_SECRET=
PAYPAL_LIVE_APP_ID=
FORTIFY_PREFIX=
-41
View File
@@ -1,41 +0,0 @@
APP_NAME=Atom
APP_ENV=testing
APP_KEY=base64:GENERATE_NEW_KEY_HERE
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mariadb
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=docker
DB_PASSWORD=password
BROADCAST_DRIVER=log
CACHE_DRIVER=array
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=array
SESSION_LIFETIME=120
MAIL_MAILER=array
# Testing specific settings
BCRYPT_ROUNDS=4
TELESCOPE_ENABLED=false
# Disable CAPTCHA for testing
GOOGLE_RECAPTCHA_ENABLED=0
CLOUDFLARE_TURNSTILE_ENABLED=0
# Cloudflare Turnstile (test keys)
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
# Google reCAPTCHA (dummy values for testing)
GOOGLE_RECAPTCHA_SITE_KEY=dummy_site_key
GOOGLE_RECAPTCHA_SECRET_KEY=dummy_secret_key
-11
View File
@@ -1,11 +0,0 @@
* text=auto
*.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
-77
View File
@@ -1,77 +0,0 @@
name: Laravel
on:
push:
branches: [ "main", "dev" ]
pull_request:
branches: [ "main", "dev" ]
jobs:
tests:
runs-on: ubuntu-latest
services:
mariadb:
image: mariadb:11
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: atomcms_testing
ports:
- 3306:3306
options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.4
- name: Install composer dependencies
run: |
composer install --no-scripts
- name: Install NPM dependencies
run: npm install
- name: Compile assets
run: npm run build:atom
- name: Prepare Laravel Application
run: |
cp .env.testing.example .env.testing
# Override database config for GitHub Actions
sed -i 's/DB_USERNAME=docker/DB_USERNAME=root/' .env.testing
sed -i 's/DB_PASSWORD=password/DB_PASSWORD=/' .env.testing
sed -i 's/DB_DATABASE=testing/DB_DATABASE=atomcms_testing/' .env.testing
php artisan key:generate --env=testing
- name: Debug Laravel Configuration
run: |
php artisan config:cache --env=testing
php artisan route:cache --env=testing
php artisan view:cache --env=testing
- name: Create storage directories
run: |
mkdir -p storage/app/public
mkdir -p storage/framework/cache
mkdir -p storage/framework/sessions
mkdir -p storage/framework/views
mkdir -p storage/logs
chmod -R 755 storage
- name: Run Test suite with debug
run: |
php artisan test --env=testing --stop-on-failure
-38
View File
@@ -1,38 +0,0 @@
name: Fix Code Style
on: [push]
permissions:
contents: write
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [8.4]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer, pint
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Run Pint
run: pint
- name: Commit linted files
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'style: fix code style with Laravel Pint'
-25
View File
@@ -1,25 +0,0 @@
/node_modules
/public/build
/public/hot
/public/client
/public/storage
/storage/*.key
/storage/app/public
/storage/framework/*
/vendor
/public/static
.env
.env.backup
.env.testing
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
yarn.lock
/.idea
/.vscode
/public/assets/images/generated-logos
CLAUDE.md
.phpunit.cache
-4
View File
@@ -1,4 +0,0 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, the owners will be requested for
# review when someone opens a pull request.
* @ObjectRetros
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright 2023 ObjectRetros
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-299
View File
@@ -1,299 +0,0 @@
<div align="center">
<img src="https://i.imgur.com/9ePNdJ4.png" alt="Atom CMS"/>
A modern, community-driven Retro CMS built with Laravel 12.x
[![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/pP6HyZedAj)
[![Laravel](https://img.shields.io/badge/Laravel-12.x-FF2D20?style=flat&logo=laravel&logoColor=white)](https://laravel.com)
[![PHP](https://img.shields.io/badge/PHP-8.2+-777BB4?style=flat&logo=php&logoColor=white)](https://php.net)
[Live Demos](#-live-preview) • [Installation](#-installation) • [Documentation](https://github.com/atom-retros/atomcms/wiki) • [Contributing](#-contributing)
</div>
>[!NOTE]
>Disclaimer: Educational Use Only
>
> Atom CMS is provided as an educational resource for learning purposes only. The creators and contributors are not responsible for any misuse or unintended consequences arising from its use. By using Atom CMS, you agree to take full responsibility for your actions and ensure compliance with all applicable laws and regulations in your jurisdiction.
## 🎯 About
Atom CMS is a modern, community-driven CMS designed to provide a flexible and user-friendly platform for retro hotel management. Built on Laravel 12.x with a focus on extensibility and ease of use, Atom CMS features a built-in theme system that allows you to use any CSS framework or create fully customized vanilla designs.
### Built With
- **[Laravel 12.x](https://laravel.com/docs/11.x)** - Elegant PHP framework powering the backend
- **[Vite](https://vitejs.dev/)** - Next-generation frontend tooling for blazing-fast builds
- **[TailwindCSS](https://tailwindcss.com/)** - Utility-first CSS framework for responsive design
---
## ✨ Features
- 🎨 **Built-in Theme System** - Use any CSS framework or create custom themes
- 🔐 **Secure Authentication** - Laravel-powered authentication and authorization
- 🌍 **Multi-language Support** - Built-in localization for global audiences
- 📊 **Integrated Housekeeping** - Comprehensive admin panel
- 🔄 **Rcon System** - Real-time server communication
- 📱 **Responsive Design** - Mobile-first approach with TailwindCSS
- 🚀 **Modern Stack** - Latest PHP 8.2+ features
---
## 👀 Live Preview
Experience Atom CMS with our official themes:
- **Dusk Theme**: [https://dusk.atomcms.dev](https://dusk.atomcms.dev)
- **Atom Theme**: [https://atom.atomcms.dev](https://atom.atomcms.dev)
---
## 🚧 Requirements
| Requirement | Version |
|------------|---------|
| PHP | 8.2 or higher |
| MySQL | 8.x or higher |
| MariaDB | 10.x or higher |
| Composer | v2 |
| Node.js | LTS |
| Database | [Arcturus Morningstar](https://github.com/ObjectRetros/retro-hotel-files/blob/main/arcturus-3-5-3-database.sql) |
### Required PHP Extensions
Ensure the following extensions are enabled in your `php.ini`:
```ini
extension=curl
extension=fileinfo
extension=gd
extension=mbstring
extension=openssl
extension=pdo_mysql
extension=sockets
extension=intl
```
**Note:** Remove the semicolon (`;`) prefix if the extension is commented out.
---
## 📦 Installation
### Windows Setup
```bash
# Clone the repository
git clone https://github.com/ObjectRetros/atomcms.git
cd atomcms
# Configure environment
copy .env.example .env
# Edit .env and update your database credentials
# Install dependencies
composer install
npm install
# Set up database and generate key
php artisan migrate --seed
php artisan key:generate
# Build assets
npm run build:atom
# For development: npm run dev:atom
```
#### IIS Configuration
Point your IIS site to the `public` folder inside the `atomcms` directory.
#### Required Permissions
Grant "Full control" to both `IUSR` and `IIS_IUSRS` for the atomcms folder.
**Visual guide:** [Permission setup tutorial](https://gyazo.com/7d5f38525a762c1b26bbd7552ca93478)
#### Troubleshooting cURL SSL Errors
If you encounter cURL 60 errors:
1. Download the latest [cacert.pem](https://curl.se/docs/caextract.html)
2. Place it in `C:/`
3. Edit `php.ini` and update:
```ini
curl.cainfo = "C:/cacert-2025-09-09.pem"
```
4. Restart your web server
#### Complete Windows Tutorial
New to retro hotel setup? Follow our comprehensive three-part series:
- [Part 1: Basic Setup](https://devbest.com/threads/how-to-set-up-a-retro-in-2022-iis-nitro-html5-part-1.92532/)
- [Part 2: Configuration](https://devbest.com/threads/how-to-set-up-a-retro-in-2022-iis-nitro-html5-part-2.92533/)
- [Part 3: Finalization](https://devbest.com/threads/how-to-set-up-a-retro-in-2022-iis-nitro-html5-part-3.92543/)
---
### Linux Setup
```bash
# Clone the repository
git clone https://github.com/ObjectRetros/atomcms.git
cd atomcms
# Configure environment
cp .env.example .env
# Edit .env and update your database credentials
# Install dependencies
composer install
npm install
# Set up database and generate key
php artisan migrate --seed
php artisan key:generate
# Build assets
npm run build:atom
# For development: npm run dev:atom
```
#### Set Permissions
```bash
sudo chown -R $USER:www-data storage
sudo chown -R $USER:www-data bootstrap/cache
chmod -R 775 storage
chmod -R 775 bootstrap/cache
```
#### NGINX Configuration
For NGINX setup, refer to Laravel's [deployment documentation](https://laravel.com/docs/12.x/deployment#nginx).
#### Complete Linux Tutorial
Need help setting up your retro hotel on Linux? Follow our comprehensive Ubuntu tutorial:
- [Complete Ubuntu Setup Guide](https://git.krews.org/duckietm/ubuntu-tutorial) - Step-by-step instructions for Ubuntu
---
## ⚙️ Configuration
### Production Environment
Update these variables in your `.env` file for production:
```dotenv
APP_ENV=production
APP_DEBUG=false
FORCE_HTTPS=true # If using Cloudflare's "Always use HTTPS"
```
### Cloudflare Turnstile Captcha
Protect your site from bots:
1. Visit [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/)
2. Sign in and select your site
3. Copy the site and secret keys to your `.env` file
### Important: Disable Rocket Loader
Atom CMS uses JavaScript that conflicts with Cloudflare's Rocket Loader. To disable:
1. Go to your Cloudflare dashboard
2. Navigate to **Speed** → **Optimization**
3. Find **Rocket Loader™** and disable it
### Migrating from Another CMS
If migrating from Cosmic CMS or similar platforms:
Set `RENAME_COLLIDING_TABLES=true` in your `.env` file. Atom CMS will automatically handle table conflicts.
**Note:** We recommend proper manual cleanup, but this feature helps avoid common migration issues.
---
## 🧪 Testing
Atom CMS includes a growing test suite using Pest.
### Run Tests
```bash
# Using Pest
vendor/bin/pest
# Using Artisan
php artisan test
```
---
## 📚 Documentation
For detailed documentation, addons, tips, and tricks, visit our [official wiki](https://github.com/atom-retros/atomcms/wiki).
### Learning Laravel
New to Laravel? These free resources will help:
- [Official bootcamp & course](https://learn.laravel.com) - Official Laravel bootcamp & course
- [Laravel 11 from Scratch](https://laracasts.com/series/30-days-to-learn-laravel-11) - Official Laracasts course
---
## 🤝 Contributing
We welcome contributions! To maintain code quality and streamline reviews, please read our [contribution guidelines](https://github.com/ObjectRetros/atomcms/wiki/0.-Contribution-guidelines) before submitting a pull request.
---
## 🏆 Credits
Atom CMS is made possible by our amazing community:
### Core Contributors
- **Kasja** - Design direction, Dusk theme, ideas & graphics
- **INicollas** - Dark mode, Turbolinks, article reactions, user sessions, PT-BR translations, Orion Housekeeping
- **Kani** - Rcon system, FindRetros API, Atom CMS v2 creator/maintainer
- **DuckieTM** - Badge drawer, bugfixes, housekeeping features
- **EntenKoeniq** - Auto language registration, color scheme selection, various page fixes
### Contributors
- **Dominic** - Performance improvements, user sessions
- **Beny** - FindRetros API fixes, Cloudflare fixes
- **Live** - French translations, bugfixes
- **MisterDeen** - Custom Discord widget
- **DamienJolly**, **Danbo**, **Diddy/Josh** - Various bugfixes and improvements
- **Sonay** - Material theme inspiration
- **Raizer** - Circinus
### Translations
- **Oliver** - Finnish
- **Damue & EntenKoeniq** - German
- **Talion** - Turkish
- **CentralCee, Rille & Tuborgs** - Swedish
- **Yannick** - Dutch
- **Gedomi** - Spanish
- **Lorenzune** - Italian
- **Twana** - Norwegian
- **Plow** - French
---
<div align="center">
**[⬆ Back to Top](#atom-cms)**
Made with ❤️ by the Atom CMS Community
</div>
@@ -1,58 +0,0 @@
<?php
namespace App\Actions\Fortify\Controllers;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Routing\Controller;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Events\RecoveryCodeReplaced;
use Laravel\Fortify\Http\Requests\TwoFactorLoginRequest;
use Laravel\Fortify\Http\Responses\TwoFactorLoginResponse;
class TwoFactorAuthenticatedSessionController extends Controller
{
/**
* The guard implementation.
*
* @var StatefulGuard
*/
protected $guard;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct(StatefulGuard $guard)
{
$this->guard = $guard;
}
/**
* Attempt to authenticate a new session using the two factor authentication code.
*/
public function store(TwoFactorLoginRequest $request): TwoFactorLoginResponse
{
$user = $request->challengedUser();
if ($code = $request->validRecoveryCode()) {
$user->replaceRecoveryCode($code);
event(new RecoveryCodeReplaced($user, $code));
} elseif (! $request->hasValidCode()) {
throw ValidationException::withMessages([
'code' => __('Invalid Two Factor Authentication code'),
]);
}
$this->guard->login($user, $request->remember());
$request->session()->regenerate();
$user->update([
'ip_current' => $request->ip(),
]);
return app(TwoFactorLoginResponse::class);
}
}
@@ -1,158 +0,0 @@
<?php
namespace App\Actions\Fortify;
use App\Actions\Fortify\Rules\PasswordValidationRules;
use App\Models\Miscellaneous\WebsiteBetaCode;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use App\Rules\BetaCodeRule;
use App\Rules\GoogleRecaptchaRule;
use App\Rules\WebsiteWordfilterRule;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*/
public function create(array $input)
{
if (setting('disable_registration') ?: '0' === '1') {
throw ValidationException::withMessages([
'registration' => __('Registration is disabled.'),
]);
}
$ip = request()?->ip();
if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
throw ValidationException::withMessages([
'registration' => __('Your IP address seems to be invalid'),
]);
}
$matchingIpCount = User::query()
->where('ip_current', '=', $ip)
->orWhere('ip_register', '=', $ip)
->count();
if ($matchingIpCount >= (int) (setting('max_accounts_per_ip') ?: 99)) {
throw ValidationException::withMessages([
'registration' => __('You have reached the max amount of allowed account'),
]);
}
$this->validate($input);
$user = User::create([
'username' => $input['username'],
'mail' => $input['mail'],
'password' => Hash::make($input['password']),
'account_created' => time(),
'last_login' => time(),
'motto' => setting('start_motto') ?: 'Welcome to the hotel!',
'look' => setting('start_look') ?: 'hr-100-61.hd-180-1.ch-210-66.lg-270-110.sh-305-62',
'credits' => setting('start_credits') ?: 1000,
'ip_register' => $ip,
'ip_current' => $ip,
'auth_ticket' => '',
'home_room' => (int) (setting('hotel_home_room') ?: 0),
]);
$user->update([
'referral_code' => sprintf('%s%s', $user->id, Str::random(8)),
]);
if (setting('requires_beta_code') !== '' && setting('requires_beta_code') !== '0') {
WebsiteBetaCode::where('code', '=', $input['beta_code'])->update([
'user_id' => $user->id,
]);
}
// Referral
if (isset($input['referral_code'])) {
$referralUser = User::query()
->where('referral_code', '=', $input['referral_code'])
->first();
if (is_null($referralUser)) {
return redirect(RouteServiceProvider::HOME);
}
// If same IP skip referral incrementation
if ($referralUser->ip_current == $user->ip_current || $referralUser->ip_register == $user->ip_register) {
return redirect(RouteServiceProvider::HOME);
}
$referralUser->referrals()->updateOrCreate(['user_id' => $referralUser->id], [
'referrals_total' => $referralUser->referrals != null ? $referralUser->referrals->referrals_total += 1 : 1,
]);
$referralUser->userReferrals()->create([
'referred_user_id' => $user->id,
'referred_user_ip' => $ip,
]);
}
if (setting('enable_discord_webhook') === '1') {
$this->sendDiscordWebhook($user->username, $user->ip_register, $user->mail);
}
return $user;
}
private function validate(array $inputs): array
{
$rules = [
'username' => ['required', 'string', sprintf('regex:%s', setting('username_regex') ?: '/^[a-zA-Z0-9_.-]+$/'), 'max:25', Rule::unique('users'), new WebsiteWordfilterRule],
'mail' => ['required', 'string', 'email', 'max:255', Rule::unique('users')],
'password' => $this->passwordRules(),
'beta_code' => ['sometimes', 'string', new BetaCodeRule],
'terms' => ['required', 'accepted'],
'g-recaptcha-response' => ['sometimes', 'string', new GoogleRecaptchaRule],
'cf-turnstile-response' => [app(Turnstile::class)],
];
$messages = [
'g-recaptcha-response.required' => __('The Google recaptcha must be completed'),
'g-recaptcha-response.string' => __('The google recaptcha was submitted with an invalid type'),
];
return Validator::make($inputs, $rules, $messages)->validate();
}
private function sendDiscordWebhook(string $username, string $ip, string $email): void
{
if (setting('discord_webhook_url') === '') {
Log::error('Discord webhook url not provided', ['Please provide a discord webhook url before being able to send any webhook requests.']);
return;
}
$request = Http::asJson()->post(setting('discord_webhook_url'), [
'username' => sprintf('%s Bot', setting('hotel_name')),
'content' => "User: {$username} has just registered, with the IP: {$ip} and E-mail: {$email}",
]);
// Log the error in-case webhook wasn't sent
if (! $request->successful()) {
Log::error('Failed to send Discord webhook notification', [
'username' => $username,
'ip' => $ip,
'email' => $email,
'response_status' => $request->status(),
'response_body' => $request->body(),
]);
}
}
}
@@ -1,16 +0,0 @@
<?php
namespace App\Actions\Fortify;
class DisableTwoFactorAuthentication extends \Laravel\Fortify\Actions\DisableTwoFactorAuthentication
{
#[\Override]
public function __invoke($user)
{
$user->forceFill([
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'two_factor_confirmed' => false,
])->save();
}
}
@@ -1,194 +0,0 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use App\Rules\GoogleRecaptchaRule;
use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Events\TwoFactorAuthenticationChallenged;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
use Laravel\Fortify\TwoFactorAuthenticatable;
use RyanChandler\LaravelCloudflareTurnstile\Rules\Turnstile;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfTwoFactorAuthenticatable
{
/**
* The guard implementation.
*
* @var StatefulGuard
*/
protected $guard;
/**
* The login rate limiter instance.
*
* @var LoginRateLimiter
*/
protected $limiter;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
$this->guard = $guard;
$this->limiter = $limiter;
}
/**
* Handle the incoming request.
*
* @return mixed
*/
public function handle(Request $request, callable $next)
{
$user = $this->validateCredentials($request);
if (Fortify::confirmsTwoFactorAuthentication()) {
if ($user?->two_factor_secret &&
! is_null($user?->two_factor_confirmed_at) &&
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user))) {
return $this->twoFactorChallengeResponse($request, $user);
} else {
return $next($request);
}
}
if ($user?->two_factor_secret &&
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user))) {
return $this->twoFactorChallengeResponse($request, $user);
}
return $next($request);
}
/**
* Attempt to validate the incoming credentials.
*
* @return mixed
*/
protected function validateCredentials(Request $request)
{
if (Fortify::$authenticateUsingCallback) {
return tap(call_user_func(Fortify::$authenticateUsingCallback, $request), function ($user) use ($request): void {
if (! $user) {
$this->fireFailedEvent($request);
$this->throwFailedAuthenticationException($request);
}
});
}
$model = $this->guard->getProvider()->getModel();
return tap($model::where(Fortify::username(), $request->{Fortify::username()})->first(), function ($user) use ($request): void {
// Update the users password to bcrypt, if they previously used md5
if ($user && config('habbo.site.convert_passwords')) {
$this->convertUserPassword($user, $request->input('password'));
}
if (! $user || ! $this->guard->getProvider()->validateCredentials($user, ['password' => $request->password])) {
$this->fireFailedEvent($request, $user);
$this->throwFailedAuthenticationException($request);
}
$this->validate($request);
$user = User::select('id', 'password', 'rank')
->where('username', '=', $request->input('username'))
->first();
if (setting('maintenance_enabled') === '1' && setting('min_maintenance_login_rank') > $user->rank) {
throw ValidationException::withMessages([
'username' => __('Only staff can login during maintenance!'),
]);
}
});
}
/**
* Throw a failed authentication validation exception.
*
*
* @throws ValidationException
*/
protected function throwFailedAuthenticationException(Request $request): void
{
$this->limiter->increment($request);
throw ValidationException::withMessages([
Fortify::username() => [trans('auth.failed')],
])->errorBag('login');
}
/**
* Fire the failed authentication attempt event with the given arguments.
*/
protected function fireFailedEvent(Request $request, ?Authenticatable $user = null): void
{
event(new Failed(config('fortify.guard'), $user, [
Fortify::username() => $request->{Fortify::username()},
'password' => $request->password,
]));
}
/**
* Get the two factor authentication enabled response.
*
* @param mixed $user
*/
protected function twoFactorChallengeResponse(Request $request, $user): Response
{
$request->session()->put([
'login.id' => $user->getKey(),
'login.remember' => $request->filled('remember'),
]);
event(new \Laravel\Fortify\Events\TwoFactorAuthenticationChallenged($user));
return $request->wantsJson()
? response()->json(['two_factor' => true])
: to_route('two-factor.login');
}
private function convertUserPassword(User $user, string $password)
{
if ($user->password == md5($password)) {
$user->update([
'password' => Hash::make($password),
]);
}
}
private function validate(Request $request): array
{
$rules = [];
if (setting('google_recaptcha_enabled') !== '' && setting('google_recaptcha_enabled') !== '0') {
$rules['g-recaptcha-response'] = ['required', 'string', new GoogleRecaptchaRule];
}
if (setting('cloudflare_turnstile_enabled') !== '' && setting('cloudflare_turnstile_enabled') !== '0') {
$rules['cf-turnstile-response'] = ['required', app(Turnstile::class)];
}
$messages = [
'g-recaptcha-response.required' => __('The Google reCAPTCHA must be completed'),
'g-recaptcha-response.string' => __('The Google reCAPTCHA was submitted with an invalid type'),
'cf-turnstile-response.required' => __('The Cloudflare Turnstile response is required'),
];
return Validator::make($request->all(), $rules, $messages)->validate();
}
}
@@ -1,10 +0,0 @@
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
class RedirectIfTwoFactorConfirmed extends RedirectIfTwoFactorAuthenticatable
{
// This class can use the default behavior from the parent class
}
@@ -1,28 +0,0 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;
/**
* Validate and reset the user's forgotten password.
*
* @param mixed $user
*/
public function reset($user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}
@@ -1,16 +0,0 @@
<?php
namespace App\Actions\Fortify\Rules;
use App\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*/
protected function passwordRules(): array
{
return ['required', 'string', new Password, 'confirmed'];
}
}
@@ -1,31 +0,0 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
class UpdateUserPassword implements UpdatesUserPasswords
{
use PasswordValidationRules;
/**
* Validate and update the user's password.
*
* @param mixed $user
*/
public function update($user, array $input): void
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current_password:web'],
'password' => $this->passwordRules(),
], [
'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword');
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}
@@ -1,57 +0,0 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* @param mixed $user
*/
public function update($user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore($user->id),
],
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}
/**
* Update the given verified user's profile information.
*
* @param mixed $user
*/
protected function updateVerifiedUser($user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();
$user->sendEmailVerificationNotification();
}
}
-38
View File
@@ -1,38 +0,0 @@
<?php
namespace App\Actions;
use App\Enums\CurrencyTypes;
use App\Services\RconService;
class SendCurrency
{
public function __construct(protected RconService $rcon) {}
public function execute($user, string $type, ?int $amount)
{
if (! $amount && $amount <= 0) {
return false;
}
if ($this->rcon->isConnected) {
match ($type) {
'credits' => $this->rcon->giveCredits($user, $amount),
'duckets' => $this->rcon->giveDuckets($user, $amount),
'diamonds' => $this->rcon->giveDiamonds($user, $amount),
'points' => $this->rcon->giveGotw($user, $amount),
default => false,
};
} else {
match ($type) {
'credits' => $user->increment('credits', $amount),
'duckets' => $user->currencies()->where('type', CurrencyTypes::Duckets)->increment('amount', $amount),
'diamonds' => $user->currencies()->where('type', CurrencyTypes::Diamonds)->increment('amount', $amount),
'points' => $user->currencies()->where('type', CurrencyTypes::Points)->increment('amount', $amount),
default => false,
};
}
return null;
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
namespace App\Actions;
use App\Models\User;
use App\Services\RconService;
class SendFurniture
{
public function __construct(private readonly RconService $rcon) {}
public function execute(User $user, array $furniture): void
{
foreach ($furniture as $furni) {
if ($this->rcon->isConnected) {
for ($i = 0; $i < $furni['amount']; $i++) {
$this->rcon->sendGift($user, $furni['item_id'], 'Thank you for supporting ' . setting('hotel_name'));
}
} else {
for ($i = 0; $i < $furni['amount']; $i++) {
$user->items()->create([
'item_id' => $furni['item_id'],
]);
}
}
}
}
}
-34
View File
@@ -1,34 +0,0 @@
<?php
namespace App\Actions;
class UserActions
{
public function updateUsername($user, $username): void
{
$user->update([
'username' => $username,
]);
}
public function updateEmail($user, $email): void
{
$user->update([
'mail' => $email,
]);
}
public function updateMotto($user, $motto): void
{
$user->update([
'motto' => $motto,
]);
}
public function updateField($user, string $field, ?string $value): void
{
$user->update([
$field => $value,
]);
}
}
@@ -1,148 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Miscellaneous\WebsiteSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class AtomSetupCommand extends Command
{
protected $signature = 'atom:setup {--auto=false}';
protected $description = 'Takes you through a basic setup, allowing you to define general settings';
private function progressInfo(int $step)
{
$this->info(sprintf('Step %s/13', $step));
$this->newLine();
}
public function handle(): void
{
Artisan::call('db:seed --class=WebsiteSettingsSeeder');
if ($this->option('auto') === 'false') {
$step = 1;
$this->progressInfo($step);
$step++;
$hotelName = $this->ask('Enter your hotel name');
WebsiteSetting::where('key', '=', 'hotel_name')->update([
'value' => empty($hotelName) ? 'Hotel' : $hotelName,
]);
$this->progressInfo($step);
$step++;
$colorMode = $this->choice('Enter your preferred CMS color mode', ['light', 'dark'], 0);
WebsiteSetting::where('key', '=', 'cms_color_mode')->update([
'value' => $colorMode,
]);
$this->progressInfo($step);
$step++;
$startCredits = $this->ask('Enter the amount of credits new users should start with: (default is 5000)');
WebsiteSetting::where('key', '=', 'start_credits')->update([
'value' => empty($startCredits) ? '5000' : $startCredits,
]);
$this->progressInfo($step);
$step++;
$startDuckets = $this->ask('Enter the amount of credits new users should start with: (default is 5000)');
WebsiteSetting::where('key', '=', 'start_duckets')->update([
'value' => empty($startDuckets) ? '5000' : $startDuckets,
]);
$this->progressInfo($step);
$step++;
$startDiamonds = $this->ask('Enter the amount of diamonds new users should start with: (default is 100)');
WebsiteSetting::where('key', '=', 'start_diamonds')->update([
'value' => empty($startDiamonds) ? '100' : $startDiamonds,
]);
$this->progressInfo($step);
$step++;
$startPoints = $this->ask('Enter the amount of points new users should start with (default is 0)');
WebsiteSetting::where('key', '=', 'start_points')->update([
'value' => empty($startPoints) ? '0' : $startPoints,
]);
$this->progressInfo($step);
$step++;
$maxAccountsPerIP = $this->ask('Enter the amount of accounts a user can register per IP address (default is 2)');
WebsiteSetting::where('key', '=', 'max_accounts_per_ip')->update([
'value' => empty($maxAccountsPerIP) ? '2' : $maxAccountsPerIP,
]);
$this->progressInfo($step);
$step++;
$recaptchaEnabled = $this->choice('Google ReCaptcha enabled: (Do not forget to add your keys to your .env file in-case you set this to 1)', ['0', '1'], 0);
WebsiteSetting::where('key', '=', 'google_recaptcha_enabled')->update([
'value' => $recaptchaEnabled,
]);
$this->progressInfo($step);
$step++;
$wordfilterEnabled = $this->choice('CMS wordfilter enabled', ['0', '1'], 1);
WebsiteSetting::where('key', '=', 'website_wordfilter_enabled')->update([
'value' => $wordfilterEnabled,
]);
$this->progressInfo($step);
$step++;
$requiredBetaCode = $this->choice('Requires beta code to register', ['0', '1'], 0);
WebsiteSetting::where('key', '=', 'requires_beta_code')->update([
'value' => $requiredBetaCode,
]);
$this->progressInfo($step);
$step++;
$registrationDisabled = $this->choice('Disable registration (Can be re-enabled later inside website_settings table if set to 1)', ['0', '1'], 0);
WebsiteSetting::where('key', '=', 'disable_registration')->update([
'value' => $registrationDisabled,
]);
$this->progressInfo($step);
$step++;
$giveHC = $this->choice('Give all new users HC automatically', ['0', '1'], 0);
WebsiteSetting::where('key', '=', 'give_hc_on_register')->update([
'value' => $giveHC,
]);
$this->progressInfo($step);
$maxCommentArticles = $this->ask('Enter the amount of comments each user can post per article (default is 2)');
WebsiteSetting::where('key', '=', 'max_comment_per_article')->update([
'value' => empty($maxCommentArticles) ? '2' : $maxCommentArticles,
]);
}
$seeders = [
'WebsiteLanguageSeeder',
'WebsiteArticleSeeder',
'WebsitePermissionSeeder',
'WebsiteWordfilterSeeder',
'WebsiteTeamSeeder',
'WebsiteRuleCategorySeeder',
'WebsiteRuleSeeder',
];
foreach ($seeders as $seeder) {
Artisan::call(sprintf('db:seed --class=%s', $seeder));
}
$this->info('The setup was successful!');
}
}
@@ -1,88 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\WebsiteAd;
use App\Services\SettingsService;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
class ImportAdsData extends Command
{
protected $signature = 'import:ads-data';
protected $description = 'Import ads data from the filesystem';
private const int CHUNK_SIZE = 100;
private const array ALLOWED_EXTENSIONS = ['jpeg', 'jpg', 'png', 'gif'];
public function handle(SettingsService $settingsService): void
{
$adsPath = $settingsService->getOrDefault('ads_path_filesystem');
if (! $this->validatePath($adsPath)) {
return;
}
$files = $this->getImageFiles($adsPath);
if ($files === []) {
$this->warn('No valid image files found in the ads directory.');
return;
}
$this->processFiles($files);
$this->info('Ads data import completed successfully.');
}
private function validatePath(?string $adsPath): bool
{
if (in_array($adsPath, [null, '', '0'], true)) {
$this->error('Ads path is not configured in website_settings.');
return false;
}
if (! is_dir($adsPath)) {
$this->error("The ads path '{$adsPath}' does not exist in the filesystem.");
return false;
}
return true;
}
private function getImageFiles(string $adsPath): array
{
return array_filter(scandir($adsPath), function ($file) use ($adsPath) {
$filePath = $adsPath . DIRECTORY_SEPARATOR . $file;
return is_file($filePath) &&
in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), self::ALLOWED_EXTENSIONS);
});
}
private function processFiles(array $files): void
{
// Get existing images to avoid duplicates
$existingImages = WebsiteAd::pluck('image')->toArray();
$newFiles = Collection::make($files)
->filter(fn ($file) => ! in_array($file, $existingImages))
->map(fn ($file) => ['image' => $file])
->values();
$skippedCount = count($files) - $newFiles->count();
if ($skippedCount > 0) {
$this->warn("Skipped {$skippedCount} existing files.");
}
$newFiles->chunk(self::CHUNK_SIZE)->each(function ($chunk): void {
WebsiteAd::insert($chunk->toArray());
$this->info('Processed ' . $chunk->count() . ' files.');
});
}
}
@@ -1,94 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\WebsiteBadge;
use App\Services\SettingsService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
class ImportBadgeData extends Command
{
protected $signature = 'import:badge-data';
protected $description = 'Import badge data from JSON file';
private const int CHUNK_SIZE = 100;
private const string BADGE_PREFIX = 'badge_desc_';
public function __construct(
private readonly SettingsService $settingsService,
) {
parent::__construct();
}
public function handle(): void
{
$jsonPath = $this->settingsService->getOrDefault('nitro_external_texts_file');
if (! $this->validateJsonFile($jsonPath)) {
return;
}
try {
$this->processBadgeData($jsonPath);
$this->info('Badge data imported successfully.');
} catch (Exception $e) {
Log::error('Failed to import badge data: ' . $e->getMessage());
$this->error('Failed to import badge data. Check the logs for details.');
}
}
private function validateJsonFile(?string $jsonPath): bool
{
if (in_array($jsonPath, [null, '', '0'], true)) {
$this->error('The JSON file path is not configured in the website settings.');
return false;
}
if (! file_exists($jsonPath)) {
$this->error('The JSON file does not exist at the specified path: ' . $jsonPath);
return false;
}
return true;
}
private function processBadgeData(string $jsonPath): void
{
$jsonData = File::json($jsonPath);
// Extract badge names and descriptions
$badgeNames = Collection::make($jsonData)
->filter(fn ($value, $key) => str_starts_with((string) $key, 'badge_name_'))
->mapWithKeys(fn ($value, $key) => [str_replace('badge_name_', '', $key) => $value]);
$badgeDescriptions = Collection::make($jsonData)
->filter(fn ($value, $key) => str_starts_with((string) $key, self::BADGE_PREFIX))
->mapWithKeys(fn ($value, $key) => [str_replace(self::BADGE_PREFIX, '', $key) => $value]);
// Combine badge names and descriptions
$badgeData = $badgeNames->map(fn ($name, $key) => [
'badge_key' => $key, // Use only the badge name (e.g., 14X12, 14XR1)
'badge_name' => $name,
'badge_description' => $badgeDescriptions->get($key, 'No description available'),
])->values();
// Upsert the combined data in chunks
$badgeData->chunk(self::CHUNK_SIZE)->each(function ($chunk): void {
WebsiteBadge::upsert(
$chunk->toArray(),
['badge_key'],
['badge_name', 'badge_description'],
);
$this->info('Processed ' . $chunk->count() . ' badges.');
});
}
}
-29
View File
@@ -1,29 +0,0 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
#[\Override]
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
#[\Override]
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}
@@ -1,31 +0,0 @@
<?php
namespace App\Enums;
enum AchievementCategory: string
{
case Identity = 'identity';
case Explore = 'explore';
case Music = 'music';
case Social = 'social';
case Games = 'games';
case RoomBuilder = 'room_builder';
case Pets = 'pets';
case Tools = 'tools';
case Events = 'events';
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public static function toInput(): array
{
$allCurrencies = self::cases();
return array_combine(
array_column($allCurrencies, 'value'),
array_column($allCurrencies, 'name'),
);
}
}
-49
View File
@@ -1,49 +0,0 @@
<?php
namespace App\Enums;
enum CurrencyTypes: int
{
case Credits = -1;
case Duckets = 0;
case Diamonds = 5;
case Points = 101;
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public static function fromCurrencyName(string $currencyName): ?self
{
$currencyName = strtolower($currencyName);
return match ($currencyName) {
'credits' => self::Credits,
'duckets' => self::Duckets,
'diamonds' => self::Diamonds,
'points' => self::Points,
default => null,
};
}
public function getImage(): string
{
return match ($this->value) {
CurrencyTypes::Credits->value => asset('assets/images/currencies/credits.gif'),
CurrencyTypes::Duckets->value => asset('assets/images/currencies/duckets.png'),
CurrencyTypes::Diamonds->value => asset('assets/images/currencies/diamonds.png'),
CurrencyTypes::Points->value => asset('assets/images/currencies/points.png'),
};
}
public static function toInput(): array
{
$allCurrencies = self::cases();
return array_combine(
array_column($allCurrencies, 'value'),
array_column($allCurrencies, 'name'),
);
}
}
-49
View File
@@ -1,49 +0,0 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
#[\Override]
public function register(): void
{
$this->reportable(function (Throwable $e): void {
//
});
}
}
@@ -1,7 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class MigrationFailedException extends Exception {}
@@ -1,7 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class RconConnectionException extends Exception {}
@@ -1,31 +0,0 @@
<?php
namespace App\Filament\Filters;
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
class DateRangeFilter extends Filter
{
#[\Override]
public static function make(?string $name = null): static
{
return parent::make($name)
->schema([
DatePicker::make("{$name}_from"),
DatePicker::make("{$name}_until"),
])
->query(function (Builder $query, array $data) use (&$name): Builder {
return $query
->when(
$data["{$name}_from"],
fn (Builder $query, $date): Builder => $query->whereDate($name, '>=', $date),
)
->when(
$data["{$name}_until"],
fn (Builder $query, $date): Builder => $query->whereDate($name, '<=', $date),
);
});
}
}
@@ -1,315 +0,0 @@
<?php
namespace App\Filament\Pages;
use App\Filament\Traits\TranslatableResource;
use App\Services\Parsers\ExternalTextsParser;
use Filament\Actions\Action as PageAction;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;
class BadgePage extends Page
{
use InteractsWithForms, TranslatableResource;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
protected string $view = 'filament.pages.badge-page';
protected static string $translateIdentifier = 'badge-resource';
public $badgeWasPreviouslyCreated;
public ?array $data = [];
public static string $roleName = 'badge_page';
public static function canAccess(): bool
{
return auth()->user()->can('view::admin::' . static::$roleName);
}
#[\Override]
public function getTitle(): string|Htmlable
{
return __(
sprintf('filament::resources.resources.%s.navigation_label', static::$translateIdentifier),
);
}
public function form(Schema $schema): Schema
{
return $schema
->components([
Section::make(__('filament::resources.tabs.Main'))
->schema([
TextInput::make('code')
->label(__('filament::resources.inputs.badge_code'))
->helperText(__('filament::resources.helpers.badge_code_helper'))
->afterStateUpdated(function (?string $state, Set $set): void {
$set('code', strtoupper($state));
})
->suffixAction(fn (): PageAction => PageAction::make('search')->icon('heroicon-o-magnifying-glass')->action(fn () => $this->searchBadgesByCode()),
),
TextInput::make('image')
->label(__('filament::resources.inputs.badge_image'))
->placeholder('...')
->autocomplete()
->visible(fn (Get $get) => isset($this->data['image']) ?? false)
->prefixAction(
fn (?string $state): PageAction => PageAction::make('visit')
->icon('heroicon-s-arrow-top-right-on-square')
->tooltip(__('filament::resources.common.Open link'))
->url($state)
->visible(fn () => ! in_array($state, [null, '', '0'], true))
->openUrlInNewTab(),
),
]),
Section::make('Nitro Texts')
->collapsible()
->visible(fn () => isset($this->data['nitro']) && ! empty($this->data['nitro']))
->schema([
TextInput::make('nitro.title')
->label(__('filament::resources.inputs.badge_title'))
->placeholder('...')
->visible(fn () => isset($this->data['nitro']['title']) ?? false),
TextInput::make('nitro.description')
->label(__('filament::resources.inputs.badge_description'))
->placeholder('...')
->visible(fn () => isset($this->data['nitro']['description']) ?? false),
]),
Section::make('Flash Texts')
->collapsible()
->visible(fn () => isset($this->data['flash']) && ! empty($this->data['flash']))
->schema([
TextInput::make('flash.title')
->label(__('filament::resources.inputs.badge_title'))
->placeholder('...')
->visible(fn () => isset($this->data['flash']['title']) ?? false),
TextInput::make('flash.description')
->label(__('filament::resources.inputs.badge_description'))
->placeholder('...')
->visible(fn () => isset($this->data['flash']['description']) ?? false),
]),
])
->statePath('data');
}
private function searchBadgesByCode(): void
{
$badgeCode = $this->form->getState()['code'] ?? null;
if (empty($badgeCode)) {
$this->notify('danger', __('filament::resources.notifications.badge_code_required'));
return;
}
$badgeData = app(ExternalTextsParser::class)->getBadgeData($badgeCode);
$this->badgeWasPreviouslyCreated = is_array($badgeData['nitro']) || is_array($badgeData['flash']);
if ($this->badgeWasPreviouslyCreated) {
Notification::make()
->icon('heroicon-o-check-circle')
->iconColor('success')
->color('success')
->title(__('filament::resources.notifications.badge_found'))
->send();
$this->data = [
'code' => $badgeCode,
...$this->getDefaultDataBehavior(
$badgeData['image'] ?? null,
$badgeData['nitro']['title'] ?? null,
$badgeData['nitro']['description'] ?? null,
$badgeData['flash']['title'] ?? null,
$badgeData['flash']['description'] ?? null,
),
];
return;
}
Notification::make()
->color('success')
->icon('heroicon-o-check-circle')
->iconColor('success')
->title(__('filament::resources.notifications.create_badge'))
->send();
$this->data = [
'code' => $badgeCode,
...$this->getDefaultDataBehavior(),
];
}
private function getDefaultDataBehavior(
?string $badgeImageUrl = null,
?string $nitroTitle = null,
?string $nitroDesc = null,
?string $flashTitle = null,
?string $flashDesc = null,
): array {
return [
'image' => $badgeImageUrl ?? '',
'nitro' => [
'title' => $nitroTitle ?? '',
'description' => $nitroDesc ?? '',
],
'flash' => [
'title' => $flashTitle ?? '',
'description' => $flashDesc ?? '',
],
];
}
public function create()
{
$nitroEnabled = config('hotel.client.nitro.enabled');
$flashEnabled = config('hotel.client.flash.enabled');
// image and code fields are required when creating a new badge
if (! $this->badgeWasPreviouslyCreated && (empty($this->data['image']) || empty($this->data['code']))) {
$notificationTitle = empty($this->data['image']) ?
__('filament::resources.notifications.badge_image_required') :
__('filament::resources.notifications.badge_code_required');
Notification::make()
->icon('heroicon-o-exclamation-triangle')
->iconColor('danger')
->color('danger')
->title($notificationTitle)
->send();
return;
}
$externalTextsParser = app(ExternalTextsParser::class);
if ((empty($this->data['nitro']) && $nitroEnabled) || (empty($this->data['flash']) && $flashEnabled)) {
Notification::make()
->icon('heroicon-o-exclamation-triangle')
->iconColor('danger')
->color('danger')
->title(__('filament::resources.notifications.badge_texts_required'))
->send();
return;
}
try {
$this->uploadBadgeImage($externalTextsParser);
if (! empty($this->data['nitro']) && $nitroEnabled) {
$externalTextsParser->updateNitroBadgeTexts($this->data['code'], ...$this->data['nitro']);
}
if (! empty($this->data['flash']) && $flashEnabled) {
$externalTextsParser->updateFlashBadgeTexts($this->data['code'], ...$this->data['flash']);
}
} catch (Throwable $exception) {
Log::channel('badge')->error('[ORION BADGE RESOURCE] - ERROR: ' . $exception->getMessage());
Notification::make()
->icon('heroicon-o-exclamation-triangle')
->iconColor('danger')
->color('danger')
->title(__('filament::resources.notifications.badge_update_failed'))
->send();
return;
}
$this->data['image'] = $externalTextsParser->getBadgeImageUrl($this->data['code']);
$this->badgeWasPreviouslyCreated = true;
Notification::make()
->icon('heroicon-o-check-circle')
->iconColor('success')
->color('success')
->title(__('filament::resources.notifications.badge_updated'))
->send();
}
protected function uploadBadgeImage(ExternalTextsParser $parser): void
{
if (empty($this->data['image']) || ! filter_var($this->data['image'], FILTER_VALIDATE_URL)) {
return;
}
if ($this->data['image'] == $parser->getBadgeImageUrl($this->data['code'])) {
return;
}
$image = Http::get($this->data['image']);
if (! $image->successful()) {
return;
}
$contentType = $image->header('content-type');
$gdImage = match ($contentType) {
'image/png' => imagecreatefrompng($this->data['image']),
'image/gif' => imagecreatefromgif($this->data['image']),
'image/jpeg' => imagecreatefromjpeg($this->data['image']),
default => false
};
if ($gdImage === false) {
Notification::make()
->icon('heroicon-o-exclamation-triangle')
->iconColor('danger')
->color('danger')
->title(__('filament::resources.notifications.badge_image_upload_failed'))
->send();
return;
}
$uploadPath = public_path(sprintf('%s%s%s.gif',
rtrim((string) config('hotel.client.flash.relative_files_path'), '\//'),
'/c_images/album1584/',
$this->data['code'],
));
imagegif($gdImage, $uploadPath);
}
/**
* @return array<\Filament\Actions\Action|ActionGroup>
*/
protected function getHeaderActions(): array
{
return [
PageAction::make('save')
->label(__('filament::resources.common.Update'))
->action(fn () => $this->create())
->color('primary')
->visible(fn () => isset($this->data['code']) && $this->badgeWasPreviouslyCreated),
PageAction::make('create')
->label(__('filament::resources.common.Create'))
->action(fn () => $this->create())
->color('success')
->visible(fn () => isset($this->data['code']) && ! $this->badgeWasPreviouslyCreated),
];
}
}
@@ -1,26 +0,0 @@
<?php
namespace App\Filament\Pages;
use App\Filament\Traits\TranslatableResource;
use Filament\Pages\Dashboard as FilamentDashboard;
class Dashboard extends FilamentDashboard
{
use TranslatableResource;
protected static string|\UnitEnum|null $navigationGroup = 'Dashboard';
protected static ?string $navigationLabel = 'Homepage';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-home';
public static string $translateIdentifier = 'dashboard';
public static string $roleName = 'dashboard';
public static function canAccess(): bool
{
return auth()->user()->can('view::admin::' . static::$roleName);
}
}
-109
View File
@@ -1,109 +0,0 @@
<?php
namespace App\Filament\Pages;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Filament\Auth\Http\Responses\Contracts\LoginResponse;
use Filament\Facades\Filament;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\TextInput;
use Filament\Models\Contracts\FilamentUser;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Component;
use Illuminate\Validation\ValidationException;
class Login extends \Filament\Auth\Pages\Login
{
public $username = '';
#[\Override]
public function authenticate(): ?LoginResponse
{
try {
$this->rateLimit(5);
} catch (TooManyRequestsException $exception) {
Notification::make()
->title(__('filament-panels::pages/auth/login.notifications.throttled.title', [
'seconds' => $exception->secondsUntilAvailable,
'minutes' => ceil($exception->secondsUntilAvailable / 60),
]))
->body(array_key_exists('body', __('filament-panels::pages/auth/login.notifications.throttled') ?: []) ? __('filament-panels::pages/auth/login.notifications.throttled.body', [
'seconds' => $exception->secondsUntilAvailable,
'minutes' => ceil($exception->secondsUntilAvailable / 60),
]) : null)
->danger()
->send();
return null;
}
$data = $this->form->getState();
if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) {
$this->throwFailureValidationException();
}
$user = Filament::auth()->user();
if (
($user instanceof FilamentUser) &&
(! $user->canAccessPanel(Filament::getCurrentOrDefaultPanel()))
) {
Filament::auth()->logout();
$this->throwFailureValidationException();
}
session()->regenerate();
return app(LoginResponse::class);
}
protected function throwFailureValidationException(): never
{
throw ValidationException::withMessages([
'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
]);
}
protected function getFormSchema(): array
{
return [
TextInput::make('username')
->label(__('filament::login.fields.username.label'))
->required()
->autocomplete(),
TextInput::make('password')
->label(__('filament::login.fields.password.label'))
->password()
->required(),
Checkbox::make('remember')
->label(__('filament::login.fields.remember.label')),
];
}
#[\Override]
protected function getEmailFormComponent(): Component
{
return TextInput::make('username')
->label(__('filament::login.fields.username.label'))
->required()
->autocomplete()
->autofocus()
->extraInputAttributes(['tabindex' => 1]);
}
/**
* @param array<string, mixed> $data
*
* @return array<string, mixed>
*/
#[\Override]
protected function getCredentialsFromFormData(array $data): array
{
return [
'username' => $data['username'],
'password' => $data['password'],
];
}
}
@@ -1,227 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles;
use App\Filament\Resources\Atom\Articles\Pages\CreateArticle;
use App\Filament\Resources\Atom\Articles\Pages\EditArticle;
use App\Filament\Resources\Atom\Articles\Pages\ListArticles;
use App\Filament\Resources\Atom\Articles\Pages\ViewArticle;
use App\Filament\Resources\Atom\Articles\RelationManagers\TagsRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\Articles\WebsiteArticle;
use Exception;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class ArticleResource extends Resource
{
use TranslatableResource;
protected static ?string $model = WebsiteArticle::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-newspaper';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/articles';
public static string $translateIdentifier = 'articles';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.Home'))
->icon('heroicon-o-home')
->schema([
TextInput::make('title')
->label(__('filament::resources.inputs.title'))
->required()
->autocomplete()
->maxLength(255)
->columnSpan('full'),
TextInput::make('short_story')
->label(__('filament::resources.inputs.description'))
->required()
->maxLength(255)
->autocomplete()
->columnSpan('full'),
FileUpload::make('image')
->label(__('filament::resources.inputs.image'))
->directory('website_news_images')
->visibility('public'),
RichEditor::make('full_story')
->label(__('filament::resources.inputs.content'))
->required()
->columnSpan('full'),
Hidden::make('user_id')
->default(fn () => auth()->check() ? auth()->user()->id : null),
]),
Tab::make(__('filament::resources.tabs.Configurations'))
->icon('heroicon-o-cog')
->schema([
Toggle::make('is_visible')
->label(__('filament::resources.inputs.visible'))
->onIcon('heroicon-s-check')
->offIcon('heroicon-s-x-mark')
->default(true)
->live()
->afterStateUpdated(function (string $operation, $state, $record): void {
if ($operation !== 'edit' || is_null($record)) {
return;
}
try {
if ($state) {
$record->restore();
} else {
$record->delete();
}
} catch (Exception $e) {
report($e);
}
})
->formatStateUsing(function ($record) {
if (is_null($record)) {
return true;
}
return is_null($record->deleted_at);
}),
Toggle::make('can_comment')
->onIcon('heroicon-s-check')
->label(__('filament::resources.inputs.allow_comments'))
->default(true)
->offIcon('heroicon-s-x-mark'),
]),
])->columnSpanFull(),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->poll('60s')
->columns(static::getTable())
->filters([
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
DeleteAction::make(),
RestoreAction::make(),
ForceDeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
RestoreBulkAction::make(),
ForceDeleteBulkAction::make(),
]);
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
ImageColumn::make('image')
->circular()
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(50)
->label(__('filament::resources.columns.image')),
TextColumn::make('title')
->label(__('filament::resources.columns.title'))
->searchable()
->limit(50),
TextColumn::make('user.username')
->searchable()
->label(__('filament::resources.columns.by')),
ToggleColumn::make('is_visible')
->label(__('filament::resources.columns.visible'))
->onIcon('heroicon-s-check')
->toggleable()
->state(fn ($record) => is_null($record->deleted_at))
->disabled(),
ToggleColumn::make('allow_comments')
->label(__('filament::resources.columns.allow_comments'))
->onIcon('heroicon-s-check')
->toggleable()
->disabled(),
];
}
#[\Override]
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
#[\Override]
public static function getRelations(): array
{
return [
TagsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => ListArticles::route('/'),
'create' => CreateArticle::route('/create'),
'view' => ViewArticle::route('/{record}'),
'edit' => EditArticle::route('/{record}/edit'),
];
}
public static function getGlobalSearchEloquentQuery(): Builder
{
return parent::getGlobalSearchEloquentQuery()->withTrashed();
}
}
@@ -1,24 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use App\Models\Article;
use Filament\Resources\Pages\CreateRecord;
class CreateArticle extends CreateRecord
{
protected static string $resource = ArticleResource::class;
protected function afterCreate(): void
{
/** @var null|Article $articleCreated */
$articleCreated = $this->getRecord();
if (! $articleCreated || ! $articleCreated->visible) {
return;
}
$articleCreated->createFollowersNotification();
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditArticle extends EditRecord
{
protected static string $resource = ArticleResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListArticles extends ListRecords
{
protected static string $resource = ArticleResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,31 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles\Pages;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class ViewArticle extends ViewRecord
{
protected static string $resource = ArticleResource::class;
public function getHeaderActions(): array
{
return [
Action::make('Send Notification')
->label(__('Send notifications'))
->color('gray')
->visible(fn (Model $record) => $record->user_id === Auth::id())
->requiresConfirmation()
->action(function (Model $record): void {
$record->createFollowersNotification();
}),
EditAction::make(),
];
}
}
@@ -1,59 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Articles\RelationManagers;
use App\Filament\Resources\Atom\Tags\TagResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\CreateAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class TagsRelationManager extends RelationManager
{
use TranslatableResource;
protected static string $relationship = 'tags';
protected static ?string $recordTitleAttribute = 'name';
public static string $translateIdentifier = 'tags';
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->columns(TagResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
CreateAction::make()
->schema(TagResource::getForm()),
AttachAction::make()->preloadRecordSelect(),
])
->recordActions([
ViewAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -1,93 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\CameraWebs;
use App\Filament\Resources\Atom\CameraWebs\Pages\EditCameraWeb;
use App\Filament\Resources\Atom\CameraWebs\Pages\ListCameraWeb;
use App\Models\Miscellaneous\CameraWeb;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class CameraWebResource extends Resource
{
protected static ?string $model = CameraWeb::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-photo';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'camera-web';
protected static ?string $pluralModelLabel = 'photos';
protected static ?string $navigationLabel = 'Web Camera';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Toggle::make('visible')
->label(__('Visible'))
->default(true),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament::resources.columns.id'))
->sortable(),
TextColumn::make('user_id')
->label(__('filament::resources.columns.user_id')),
TextColumn::make('room_id')
->label(__('filament::resources.columns.room_id')),
TextColumn::make('timestamp')
->label(__('filament::resources.columns.created_at'))
->dateTime(),
ImageColumn::make('url')
->label(__('filament::resources.columns.image'))
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(125),
ToggleColumn::make('visible')
->label(__('Visible')),
])
->recordActions([
DeleteAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
#[\Override]
public static function getRelations(): array
{
return [
];
}
public static function getPages(): array
{
return [
'index' => ListCameraWeb::route('/'),
'edit' => EditCameraWeb::route('/{record}/edit'),
];
}
public static function canCreate(): bool
{
return false;
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\CameraWebs\Pages;
use App\Filament\Resources\Atom\CameraWebs\CameraWebResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditCameraWeb extends EditRecord
{
protected static string $resource = CameraWebResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\CameraWebs\Pages;
use App\Filament\Resources\Atom\CameraWebs\CameraWebResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListCameraWeb extends ListRecords
{
protected static string $resource = CameraWebResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,112 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\CmsSettings;
use App\Filament\Resources\Atom\CmsSettings\Pages\ManageCmsSettings;
use App\Filament\Traits\TranslatableResource;
use App\Models\Miscellaneous\WebsiteSetting;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class CmsSettingResource extends Resource
{
use TranslatableResource;
protected static ?string $model = WebsiteSetting::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cpu-chip';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/cms-settings';
public static string $translateIdentifier = 'cms-settings';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('key')
->label(__('filament::resources.inputs.key'))
->maxLength(50)
->autocomplete()
->unique(ignoreRecord: true)
->required(),
TextInput::make('value')
->label(__('filament::resources.inputs.value'))
->required()
->maxLength(255)
->autocomplete(),
TextInput::make('comment')
->label(__('filament::resources.inputs.comment'))
->nullable()
->maxLength(255)
->autocomplete()
->columnSpanFull(),
])
->columns([
'sm' => 2,
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('key')
->label(__('filament::resources.columns.key'))
->searchable(),
TextColumn::make('value')
->label(__('filament::resources.columns.value'))
->searchable()
->limit(30),
TextColumn::make('comment')
->label(__('filament::resources.columns.comment'))
->toggleable()
->searchable()
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
})
->limit(60),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
// ...
]);
}
public static function getPages(): array
{
return [
'index' => ManageCmsSettings::route('/'),
];
}
}
@@ -1,24 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\CmsSettings\Pages;
use App\Filament\Resources\Atom\CmsSettings\CmsSettingResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
class ManageCmsSettings extends ManageRecords
{
protected static string $resource = CmsSettingResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
protected function getTableRecordsPerPageSelectOptions(): array
{
return [25, 50, 100];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Resources\Pages\CreateRecord;
class CreateHelpQuestionCategory extends CreateRecord
{
protected static string $resource = HelpQuestionCategoryResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditHelpQuestionCategory extends EditRecord
{
protected static string $resource = HelpQuestionCategoryResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,24 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHelpQuestionCategories extends ListRecords
{
protected static string $resource = HelpQuestionCategoryResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
protected function getTableReorderColumn(): ?string
{
return 'order';
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use Filament\Resources\Pages\ViewRecord;
class ViewHelpQuestionCategory extends ViewRecord
{
protected static string $resource = HelpQuestionCategoryResource::class;
}
@@ -1,48 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionCategoryResource\RelationManagers;
use App\Filament\Resources\Atom\HelpQuestionResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class QuestionsRelationManager extends RelationManager
{
use TranslatableResource;
protected static string $relationship = 'questions';
protected static ?string $recordTitleAttribute = 'title';
public static string $translateIdentifier = 'help-questions';
protected static ?string $inverseRelationship = 'categories';
public function form(Schema $schema): Schema
{
return $schema->components(HelpQuestionResource::getForm(true));
}
public function table(Table $table): Table
{
return $table->columns(HelpQuestionResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
AttachAction::make(),
])
->recordActions([
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Resources\Pages\CreateRecord;
class CreateHelpQuestion extends CreateRecord
{
protected static string $resource = HelpQuestionResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditHelpQuestion extends EditRecord
{
protected static string $resource = HelpQuestionResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHelpQuestions extends ListRecords
{
protected static string $resource = HelpQuestionResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\Pages;
use App\Filament\Resources\Atom\HelpQuestionResource;
use Filament\Resources\Pages\ViewRecord;
class ViewHelpQuestion extends ViewRecord
{
protected static string $resource = HelpQuestionResource::class;
}
@@ -1,52 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HelpQuestionResource\RelationManagers;
use App\Filament\Resources\Atom\HelpQuestionCategoryResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\CreateAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\EditAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class CategoriesRelationManager extends RelationManager
{
use TranslatableResource;
protected static string $relationship = 'categories';
protected static ?string $recordTitleAttribute = 'name';
public static string $translateIdentifier = 'help-question-categories';
protected static ?string $inverseRelationship = 'questions';
public function form(Schema $schema): Schema
{
return $schema->components(HelpQuestionCategoryResource::getForm());
}
public function table(Table $table): Table
{
return $table->columns(HelpQuestionCategoryResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest('id'))
->filters([
//
])
->headerActions([
CreateAction::make(),
AttachAction::make(),
])
->recordActions([
EditAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -1,119 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HousekeepingPermissions;
use App\Filament\Resources\Atom\HousekeepingPermissions\Pages\ListHousekeepingPermissions;
use App\Models\WebsiteHousekeepingPermission;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class HousekeepingPermissionResource extends Resource
{
protected static ?string $model = WebsiteHousekeepingPermission::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/housekeeping-permissions';
protected static ?string $navigationLabel = 'Housekeeping permissions';
public static string $translateIdentifier = 'housekeeping-permissions';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('permission')
->label(__('filament::resources.inputs.permission'))
->maxLength(50)
->autocomplete()
->unique(ignoreRecord: true)
->required(),
TextInput::make('min_rank')
->label(__('filament::resources.inputs.min_rank'))
->required()
->maxLength(255)
->autocomplete(),
TextInput::make('description')
->label(__('filament::resources.inputs.description'))
->nullable()
->maxLength(255)
->autocomplete()
->columnSpanFull(),
])
->columns([
'sm' => 2,
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'asc')
->columns([
TextColumn::make('permission')
->label(__('filament::resources.columns.permission'))
->searchable(),
TextColumn::make('min_rank')
->label(__('filament::resources.columns.min_rank'))
->searchable()
->limit(30),
TextColumn::make('description')
->label(__('filament::resources.columns.description'))
->toggleable()
->searchable()
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
})
->limit(60),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
//
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListHousekeepingPermissions::route('/'),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\HousekeepingPermissions\Pages;
use App\Filament\Resources\Atom\HousekeepingPermissions\HousekeepingPermissionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListHousekeepingPermissions extends ListRecords
{
protected static string $resource = HousekeepingPermissionResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateNavigation extends CreateRecord
{
protected static string $resource = NavigationResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditNavigation extends EditRecord
{
protected static string $resource = NavigationResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\NavigationResource\Pages;
use App\Filament\Resources\Atom\NavigationResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListNavigations extends ListRecords
{
protected static string $resource = NavigationResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,92 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\NavigationResource\RelationManagers;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\DissociateAction;
use Filament\Actions\DissociateBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
class SubNavigationsRelationManager extends RelationManager
{
protected static string $relationship = 'subNavigations';
protected static ?string $recordTitleAttribute = 'label';
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('label')
->label(__('filament::resources.inputs.label'))
->columnSpanFull()
->required()
->maxLength(255),
TextInput::make('slug')
->label(__('filament::resources.inputs.slug')),
TextInput::make('order')
->numeric()
->minValue(0)
->default(0)
->label(__('filament::resources.columns.order')),
Toggle::make('visible')
->label(__('filament::resources.columns.visible')),
Toggle::make('new_tab')
->label(__('filament::resources.columns.new_tab')),
])
->columns([
'sm' => 2,
]);
}
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('label'),
TextColumn::make('slug')
->label(__('filament::resources.columns.slug')),
ToggleColumn::make('visible')
->label(__('filament::resources.columns.visible')),
ToggleColumn::make('new_tab')
->label(__('filament::resources.columns.new_tab')),
TextColumn::make('order')
->label(__('filament::resources.columns.order')),
])
->reorderable('order')
->filters([
//
])
->headerActions([
CreateAction::make(),
// Tables\Actions\AssociateAction::make(),
])
->recordActions([
EditAction::make(),
DissociateAction::make(),
DeleteAction::make(),
])
->toolbarActions([
DissociateBulkAction::make(),
DeleteBulkAction::make(),
]);
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Resources\Pages\CreateRecord;
class CreatePermission extends CreateRecord
{
protected static string $resource = PermissionResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditPermission extends EditRecord
{
protected static string $resource = PermissionResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListPermissions extends ListRecords
{
protected static string $resource = PermissionResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Permissions\Pages;
use App\Filament\Resources\Atom\Permissions\PermissionResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewPermission extends ViewRecord
{
protected static string $resource = PermissionResource::class;
public function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}
@@ -1,268 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Permissions;
use App\Filament\Resources\Atom\Permissions\Pages\CreatePermission;
use App\Filament\Resources\Atom\Permissions\Pages\EditPermission;
use App\Filament\Resources\Atom\Permissions\Pages\ListPermissions;
use App\Filament\Resources\Atom\Permissions\Pages\ViewPermission;
use App\Filament\Tables\Columns\HabboBadgeColumn;
use App\Filament\Traits\TranslatableResource;
use App\Models\Game\Permission;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Pages\Enums\SubNavigationPosition;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\HtmlString;
use Str;
class PermissionResource extends Resource
{
use TranslatableResource;
protected static ?string $model = Permission::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/permissions';
public static string $translateIdentifier = 'permissions';
protected static ?string $recordTitleAttribute = 'rank_name';
protected static ?\Filament\Pages\Enums\SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Top;
#[\Override]
public static function form(\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
{
/**
* @param string $name
* @param bool $needsSecondOption = false
*/
$groupedToggleButton = fn (string $name, bool $needsSecondOption = false): ToggleButtons => ToggleButtons::make($name)
->label(function () use ($name) {
$translationKey = "filament::resources.permissions.{$name}";
$translation = __($translationKey);
if ($translationKey == $translation) {
return $name;
}
return $translation;
})
->options(function () use ($needsSecondOption) {
$options = [
'0' => __('filament::resources.options.no'),
'1' => __('filament::resources.options.yes'),
];
if ($needsSecondOption) {
$options['2'] = __('filament::resources.options.rights');
}
return $options;
})
->icons(['0' => 'heroicon-o-check', '1' => 'heroicon-o-x-mark', '2' => 'heroicon-o-sparkles'])
->colors(['0' => 'danger', '1' => 'success'])
->grouped();
return $schema
->components([
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.General Information'))
->schema([
TextInput::make('rank_name')
->label(__('filament::resources.inputs.name'))
->maxLength(25)
->required(),
TextInput::make('badge')
->label(__('filament::resources.inputs.badge_code'))
->maxLength(12)
->required(),
TextInput::make('level')
->label(__('filament::resources.inputs.level'))
->required(),
TextInput::make('room_effect')
->label(__('filament::resources.inputs.room_effect'))
->required(),
]),
Tab::make(__('filament::resources.tabs.In-game Permissions'))
->schema([
Section::make(__('filament::resources.sections.permissions.title'))
->description(new HtmlString(__('filament::resources.sections.permissions.description')))
->schema([
Grid::make()
->columns([
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema(function () use ($groupedToggleButton) {
$columns = Schema::getColumns('permissions');
$arcturusPermissions = collect($columns)->filter(function (array $column) {
$columnName = $column['name'] ?? null;
if (! $columnName) {
return false;
}
return str_starts_with($columnName, 'cmd')
|| str_starts_with($columnName, 'acc')
|| str_ends_with($columnName, 'cmd');
})->values();
return $arcturusPermissions->map(function (array $column) use ($groupedToggleButton) {
$columnName = $column['name'];
$needsSecondOption = $column['type_name'] == 'enum' && str_ends_with((string) $column['type'], "'2')");
return $groupedToggleButton($columnName, $needsSecondOption);
})->toArray();
}),
]),
]),
Tab::make(__('filament::resources.tabs.Configurations'))
->schema([
Grid::make(['default' => 2])
->schema([
Select::make('log_commands')
->label(__('filament::resources.inputs.log_commands'))
->columnSpanFull()
->options([
'0' => __('filament::resources.options.no'),
'1' => __('filament::resources.options.yes'),
]),
TextInput::make('prefix')
->label(__('filament::resources.inputs.prefix'))
->maxLength(5)
->required(),
ColorPicker::make('prefix_color')
->label(__('filament::resources.inputs.prefix_color'))
->required(),
Toggle::make('hidden_rank')
->label(__('filament::resources.inputs.is_hidden'))
->columnSpanFull(),
Section::make()
->schema([
Grid::make()
->columns([
'md' => 2,
])
->schema([
TextInput::make('auto_credits_amount')
->columnSpan(1)
->label(__('filament::resources.inputs.auto_credits_amount'))
->required(),
TextInput::make('auto_pixels_amount')
->label(__('filament::resources.inputs.auto_pixels_amount'))
->required(),
TextInput::make('auto_gotw_amount')
->label(__('filament::resources.inputs.auto_gotw_amount'))
->required(),
TextInput::make('auto_points_amount')
->label(__('filament::resources.inputs.auto_points_amount'))
->required(),
]),
]),
]),
]),
])
->columnSpanFull()
->persistTabInQueryString(),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
HabboBadgeColumn::make('badge')
->alignCenter()
->label(__('filament::resources.columns.image')),
TextColumn::make('rank_name')
->label(__('filament::resources.columns.name'))
->description(fn (Model $record) => Str::limit($record->description, 40))
->tooltip(function (Model $record): ?string {
$description = $record->description;
if (strlen($description) <= 40) {
return null;
}
return $description;
})
->searchable(),
TextColumn::make('prefix')
->label(__('filament::resources.columns.prefix'))
->description(fn (Model $record) => $record->prefix_color)
->searchable(),
ToggleColumn::make('hidden_rank')
->label(__('filament::resources.columns.is_hidden')),
])
->filters([
//
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListPermissions::route('/'),
'create' => CreatePermission::route('/create'),
'view' => ViewPermission::route('/{record}'),
'edit' => EditPermission::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTag extends CreateRecord
{
protected static string $resource = TagResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditTag extends EditRecord
{
protected static string $resource = TagResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListTags extends ListRecords
{
protected static string $resource = TagResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags\Pages;
use App\Filament\Resources\Atom\Tags\TagResource;
use Filament\Resources\Pages\ViewRecord;
class ViewTag extends ViewRecord
{
protected static string $resource = TagResource::class;
}
@@ -1,52 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags\RelationManagers;
use App\Filament\Resources\Atom\Articles\ArticleResource;
use App\Filament\Traits\TranslatableResource;
use Filament\Actions\AttachAction;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Actions\ViewAction;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class ArticlesRelationManager extends RelationManager
{
use TranslatableResource;
// Use camelCase to match the method in the Tag model
protected static string $relationship = 'websiteArticles';
protected static ?string $recordTitleAttribute = 'title';
public static string $translateIdentifier = 'article';
public function form(Schema $schema): Schema
{
return $schema
->components(ArticleResource::getForm());
}
public function table(Table $table): Table
{
return $table
->columns(ArticleResource::getTable())
->modifyQueryUsing(fn ($query) => $query->latest())
->filters([
//
])
->headerActions([
AttachAction::make()
->preloadRecordSelect(),
])
->recordActions([
ViewAction::make(),
DetachAction::make(),
])
->toolbarActions([
DetachBulkAction::make(),
]);
}
}
@@ -1,125 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Tags;
use App\Filament\Resources\Atom\Tags\Pages\CreateTag;
use App\Filament\Resources\Atom\Tags\Pages\EditTag;
use App\Filament\Resources\Atom\Tags\Pages\ListTags;
use App\Filament\Resources\Atom\Tags\Pages\ViewTag;
use App\Filament\Resources\Atom\Tags\RelationManagers\ArticlesRelationManager;
use App\Filament\Traits\TranslatableResource;
use App\Models\Articles\Tag;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ColorColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class TagResource extends Resource
{
use TranslatableResource;
protected static ?string $model = Tag::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/tags';
public static string $translateIdentifier = 'tags';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components(static::getForm());
}
public static function getForm(): array
{
return [
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.Home'))
->icon('heroicon-o-home')
->schema([
TextInput::make('name')
->label(__('filament::resources.inputs.name'))
->required()
->maxLength(255)
->autocomplete()
->columnSpan('full'),
ColorPicker::make('background_color')
->label(__('filament::resources.inputs.background_color'))
->required()
->columnSpan('full'),
]),
])->columnSpanFull(),
];
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns(static::getTable())
->filters([
//
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
public static function getTable(): array
{
return [
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
TextColumn::make('name')
->label(__('filament::resources.columns.name'))
->searchable()
->limit(50),
ColorColumn::make('background_color')
->label(__('filament::resources.columns.background_color'))
->searchable()
->copyable()
->copyMessage(__('filament::resources.common.Sucessfull'))
->copyMessageDuration(1500),
];
}
#[\Override]
public static function getRelations(): array
{
return [
ArticlesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => ListTags::route('/'),
'create' => CreateTag::route('/create'),
'view' => ViewTag::route('/{record}'),
'edit' => EditTag::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTeam extends CreateRecord
{
protected static string $resource = TeamResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditTeam extends EditRecord
{
protected static string $resource = TeamResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Teams\Pages;
use App\Filament\Resources\Atom\Teams\TeamResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListTeams extends ListRecords
{
protected static string $resource = TeamResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,118 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\Teams;
use App\Filament\Resources\Atom\Teams\Pages\CreateTeam;
use App\Filament\Resources\Atom\Teams\Pages\EditTeam;
use App\Filament\Resources\Atom\Teams\Pages\ListTeams;
use App\Filament\Tables\Columns\HabboBadgeColumn;
use App\Filament\Traits\TranslatableResource;
use App\Models\Community\Staff\WebsiteTeam;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class TeamResource extends Resource
{
use TranslatableResource;
protected static ?string $model = WebsiteTeam::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-group';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'website/teams';
public static string $translateIdentifier = 'teams';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Section::make()
->schema([
TextInput::make('rank_name')
->autofocus()
->maxLength(255)
->required()
->label(__('filament::resources.inputs.name')),
TextInput::make('job_description')
->maxLength(255)
->label(__('filament::resources.inputs.description')),
TextInput::make('badge')
->maxLength(255)
->label(__('filament::resources.inputs.badge_code'))
->required(),
Toggle::make('hidden_rank')
->label(__('filament::resources.inputs.is_hidden')),
]),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
HabboBadgeColumn::make('badge')
->label(__('filament::resources.columns.badge')),
TextColumn::make('rank_name')
->label(__('filament::resources.columns.name')),
TextColumn::make('job_description')
->label(__('filament::resources.inputs.description')),
IconColumn::make('hidden_rank')
->label(__('filament::resources.columns.is_hidden'))
->icon(fn (Model $record) => $record->hidden_rank ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle')
->colors([
'danger' => false,
'success' => true,
]),
])
->filters([
//
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
DeleteBulkAction::make(),
]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListTeams::route('/'),
'create' => CreateTeam::route('/create'),
'edit' => EditTeam::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\WebsiteDrawBadges\Pages;
use App\Filament\Resources\Atom\WebsiteDrawBadges\WebsiteDrawBadgeResource;
use Filament\Resources\Pages\EditRecord;
class EditWebsiteDrawBadge extends EditRecord
{
protected static string $resource = WebsiteDrawBadgeResource::class;
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\WebsiteDrawBadges\Pages;
use App\Filament\Resources\Atom\WebsiteDrawBadges\WebsiteDrawBadgeResource;
use Filament\Resources\Pages\ListRecords;
class ListWebsiteDrawBadge extends ListRecords
{
protected static string $resource = WebsiteDrawBadgeResource::class;
}
@@ -1,176 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\WebsiteDrawBadges;
use App\Filament\Resources\Atom\WebsiteDrawBadges\Pages\EditWebsiteDrawBadge;
use App\Filament\Resources\Atom\WebsiteDrawBadges\Pages\ListWebsiteDrawBadge;
use App\Models\WebsiteDrawBadge;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\DB;
class WebsiteDrawBadgeResource extends Resource
{
protected static ?string $model = WebsiteDrawBadge::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-trophy';
protected static string|\UnitEnum|null $navigationGroup = 'Website';
protected static ?string $slug = 'draw-badges';
protected static ?string $pluralModelLabel = 'draw badges';
protected static ?string $navigationLabel = 'Draw Badges';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('badge_name')
->label(__('Badge Name'))
->nullable()
->maxLength(24)
->autocomplete(false),
TextInput::make('badge_desc')
->label(__('Badge Description'))
->nullable()
->maxLength(255)
->autocomplete(false)
->columnSpanFull(),
Toggle::make('published')
->label(__('Published'))
->default(false),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('ID'))
->sortable(),
TextColumn::make('user_id')
->label(__('User ID')),
TextColumn::make('user.username')
->label(__('Username'))
->sortable()
->searchable(),
TextColumn::make('badge_name')
->limit(8)
->label(__('Badge Name')),
TextColumn::make('badge_desc')
->label(__('Badge description'))
->limit(35)
->tooltip(function (TextColumn $column): ?string {
$state = $column->getState();
if (strlen($state) <= $column->getCharacterLimit()) {
return null;
}
return $state;
}),
TextColumn::make('created_at')
->label(__('Created At'))
->dateTime(),
ImageColumn::make('badge_url')
->label(__('Badge'))
->getStateUsing(fn ($record) => config('app.url') . $record->badge_url)
->extraAttributes(['style' => 'image-rendering: pixelated'])
->size(40),
ToggleColumn::make('published')
->label(__('Published')),
])
->recordActions([
DeleteAction::make()
->before(function (DeleteAction $action, WebsiteDrawBadge $record): void {
$badgeCode = pathinfo($record->badge_path, PATHINFO_FILENAME);
// Remove the badge from any user before deleting it.
if ($record->published) {
DB::table('users_badges')
->where('user_id', $record->user_id)
->where('badge_code', $badgeCode)
->delete();
}
// Remove from JSON
$filePath = DB::table('website_settings')->where('key', 'nitro_external_texts_file')->value('value');
if ($filePath && file_exists($filePath) && is_writable($filePath)) {
$json = json_decode(file_get_contents($filePath), true);
unset($json["badge_name_{$badgeCode}"]);
unset($json["badge_desc_{$badgeCode}"]);
file_put_contents($filePath, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
// Delete the badge file from the filesystem
$badgePath = $record->badge_path;
if ($badgePath && file_exists($badgePath)) {
unlink($badgePath);
}
}),
])
->toolbarActions([
DeleteBulkAction::make()
->before(function (DeleteBulkAction $action, $records): void {
foreach ($records as $record) {
$badgeCode = pathinfo((string) $record->badge_path, PATHINFO_FILENAME);
// Remove the badge from any user before deleting it.
if ($record->published) {
DB::table('users_badges')
->where('user_id', $record->user_id)
->where('badge_code', $badgeCode)
->delete();
}
$filePath = DB::table('website_settings')->where('key', 'nitro_external_texts_file')->value('value');
if ($filePath && file_exists($filePath) && is_writable($filePath)) {
$json = json_decode(file_get_contents($filePath), true);
unset($json["badge_name_{$badgeCode}"]);
unset($json["badge_desc_{$badgeCode}"]);
file_put_contents($filePath, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
$badgePath = $record->badge_path;
if ($badgePath && file_exists($badgePath)) {
unlink($badgePath);
}
}
}),
]);
}
#[\Override]
public static function getRelations(): array
{
return [];
}
public static function getPages(): array
{
return [
'index' => ListWebsiteDrawBadge::route('/'),
'edit' => EditWebsiteDrawBadge::route('/{record}/edit'),
];
}
public static function canCreate(): bool
{
return false;
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Atom\WriteableBoxResource\Pages;
use App\Filament\Resources\Atom\WriteableBoxResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
class ManageWriteableBoxes extends ManageRecords
{
protected static string $resource = WriteableBoxResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,25 +0,0 @@
<?php
namespace App\Filament\Resources\DashboardResource\Widgets;
use App\Filament\Resources\Shop\ShopOrderResource;
use App\Models\User\UserOrder;
use Filament\Actions\ViewAction;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class LatestOrders extends BaseWidget
{
protected int|string|array $columnSpan = 'full';
public function table(Table $table): Table
{
return $table
->query(UserOrder::latest())
->paginated([3, 5, 8])
->columns(ShopOrderResource::getTable())
->recordActions([
ViewAction::make()->schema(ShopOrderResource::getForm()),
]);
}
}
@@ -1,79 +0,0 @@
<?php
namespace App\Filament\Resources\DashboardResource\Widgets;
use App\Models\User\UserOrder;
use Filament\Widgets\ChartWidget;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Illuminate\Contracts\Support\Htmlable;
class OrdersAggregateChart extends ChartWidget
{
protected ?string $maxHeight = '300px';
protected string $color = 'secondary';
#[\Override]
public function getHeading(): string|Htmlable|null
{
return __('filament::resources.stats.orders_chart.title');
}
#[\Override]
public function getDescription(): string|Htmlable|null
{
return __('filament::resources.stats.orders_chart.description');
}
#[\Override]
protected function getData(): array
{
$pendingOrder = Trend::query(UserOrder::pending())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$cancelledOrder = Trend::query(UserOrder::cancelled())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$completedOrder = Trend::query(UserOrder::completed())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$datasets = [
$this->getDataset($pendingOrder, __('filament::resources.stats.orders_chart.pending'), '#fbbf24', '#f59e0b'),
$this->getDataset($cancelledOrder, __('filament::resources.stats.orders_chart.cancelled'), '#dc2626', '#b91c1c'),
$this->getDataset($completedOrder, __('filament::resources.stats.orders_chart.completed'), '#10b981', '#059669'),
];
$data = $pendingOrder->map(fn (TrendValue $value) => $value->date)->merge(
$cancelledOrder->map(fn (TrendValue $value) => $value->date),
)->merge(
$completedOrder->map(fn (TrendValue $value) => $value->date),
)->unique()->sort()->flatten();
return [
'datasets' => $datasets,
'labels' => $data,
];
}
protected function getDataset($data, $label, string $backgroundColor, string $borderColor): array
{
return [
'label' => $label,
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
'backgroundColor' => $backgroundColor,
'borderColor' => $borderColor,
];
}
protected function getType(): string
{
return 'bar';
}
}
@@ -1,176 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\Achievements;
use App\Enums\AchievementCategory;
use App\Enums\CurrencyTypes;
use App\Filament\Resources\Hotel\Achievements\Pages\CreateAchievement;
use App\Filament\Resources\Hotel\Achievements\Pages\EditAchievement;
use App\Filament\Resources\Hotel\Achievements\Pages\ListAchievements;
use App\Filament\Resources\Hotel\Achievements\Pages\ViewAchievement;
use App\Filament\Tables\Columns\HabboBadgeColumn;
use App\Filament\Traits\TranslatableResource;
use App\Models\Achievement;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
class AchievementResource extends Resource
{
use TranslatableResource;
protected static ?string $model = Achievement::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-academic-cap';
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
public static string $translateIdentifier = 'achievements';
protected static ?string $slug = 'hotel/achievements';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
Tabs::make('Main')
->tabs([
Tab::make(__('filament::resources.tabs.Home'))
->icon('heroicon-o-home')
->schema([
TextInput::make('name')
->label(__('filament::resources.inputs.name'))
->required()
->maxLength(64)
->autocomplete()
->columnSpan('full'),
TextInput::make('level')
->label(__('filament::resources.inputs.level'))
->numeric()
->required()
->autocomplete()
->columnSpan('full'),
Select::make('category')
->native(false)
->label(__('filament::resources.inputs.category'))
->options(AchievementCategory::toInput()),
]),
Tab::make(__('filament::resources.tabs.Configurations'))
->icon('heroicon-o-cog')
->schema([
Select::make('visible')
->native(false)
->label(__('filament::resources.inputs.visible'))
->options([
'1' => __('filament::resources.common.Yes'),
'0' => __('filament::resources.common.No'),
]),
Select::make('reward_type')
->native(false)
->label(__('filament::resources.inputs.reward_type'))
->options(CurrencyTypes::toInput()),
TextInput::make('reward_amount')
->label(__('filament::resources.inputs.reward_amount'))
->numeric()
->required(),
TextInput::make('points')
->label(__('filament::resources.inputs.points'))
->helperText(__('filament::resources.helpers.achievement_points'))
->numeric()
->required(),
TextInput::make('progress_needed')
->label(__('filament::resources.inputs.progress_needed'))
->helperText(__('filament::resources.helpers.achievement_progress_needed'))
->numeric()
->required(),
]),
])->columnSpanFull(),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament::resources.columns.id')),
HabboBadgeColumn::make('badge')
->label(__('filament::resources.columns.badge')),
TextColumn::make('name')
->label(__('filament::resources.columns.name'))
->searchable(),
TextColumn::make('level')
->label(__('filament::resources.columns.level')),
TextColumn::make('category')
->badge()
->searchable()
->label(__('filament::resources.columns.category'))
->toggleable(),
ToggleColumn::make('visible')
->label(__('filament::resources.columns.visible'))
->disabled()
->toggleable(),
])
->filters([
SelectFilter::make('visible')
->options([
'1' => __('filament::resources.common.Yes'),
'0' => __('filament::resources.common.No'),
])
->label(__('filament::resources.columns.visible'))
->placeholder(__('filament::resources.common.All')),
SelectFilter::make('category')
->options(AchievementCategory::toInput())
->label(__('filament::resources.columns.category'))
->placeholder(__('filament::resources.common.All')),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([]);
}
#[\Override]
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListAchievements::route('/'),
'create' => CreateAchievement::route('/create'),
'view' => ViewAchievement::route('/{record}'),
'edit' => EditAchievement::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\Achievements\Pages;
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
use Filament\Resources\Pages\CreateRecord;
class CreateAchievement extends CreateRecord
{
protected static string $resource = AchievementResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\Achievements\Pages;
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\EditRecord;
class EditAchievement extends EditRecord
{
protected static string $resource = AchievementResource::class;
protected function getActions(): array
{
return [
// Actions\DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\Achievements\Pages;
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
class ListAchievements extends ListRecords
{
protected static string $resource = AchievementResource::class;
protected function getActions(): array
{
return [
// Actions\CreateAction::make(),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\Achievements\Pages;
use App\Filament\Resources\Hotel\Achievements\AchievementResource;
use Filament\Resources\Pages\ViewRecord;
class ViewAchievement extends ViewRecord
{
protected static string $resource = AchievementResource::class;
}
@@ -1,101 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeTextEditors;
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\CreateBadgeTextEditor;
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\EditBadgeTextEditor;
use App\Filament\Resources\Hotel\BadgeTextEditors\Pages\ListBadgeTextEditors;
use App\Models\WebsiteBadge;
use App\Services\SettingsService;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
class BadgeTextEditorResource extends Resource
{
protected static ?string $model = WebsiteBadge::class;
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-pencil-square';
protected static ?string $navigationLabel = 'Badge Editor';
protected static ?string $modelLabel = 'Badge Text';
protected static ?string $slug = 'hotel/badge-text-editor';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('badge_key')
->required()
->label('Badge Key - Expl. ATOM101')
->placeholder('This is the badge code'),
TextInput::make('badge_name')
->required()
->label('Badge Name')
->placeholder('This is the name of the badge: Expl. The ATOM Badge'),
Textarea::make('badge_description')
->required()
->label('Badge Description')
->placeholder('Please add a description for the badge.'),
]);
}
#[\Override]
public static function table(Table $table): Table
{
$settingsService = app(SettingsService::class);
$badgesPath = $settingsService->getOrDefault('badges_path', '/gamedata/c_images/album1584/');
return $table
->columns([
ImageColumn::make('badge_key')
->label('Badge Image')
->getStateUsing(function ($record) use ($badgesPath) {
$badgeName = str_replace('badge_desc_', '', $record->badge_key);
return asset($badgesPath . $badgeName . '.gif');
})
->width(50)
->height(50),
TextColumn::make('badge_name')
->label('Badge Code & Name')
->formatStateUsing(fn ($record) => $record->badge_key . ' : ' . $record->badge_name)
->searchable(query: function ($query, $search): void {
$query->where('badge_key', 'like', "%{$search}%")
->orWhere('badge_name', 'like', "%{$search}%");
})
->sortable(),
TextColumn::make('badge_description')
->label('Badge Description')
->getStateUsing(fn ($record) => Str::limit($record->badge_description, 65))
->searchable(),
])
->filters([])
->defaultSort('badge_key', 'asc')
->recordActions([
EditAction::make(),
DeleteAction::make(),
]);
}
public static function getPages(): array
{
return [
'index' => ListBadgeTextEditors::route('/'),
'create' => CreateBadgeTextEditor::route('/create'),
'edit' => EditBadgeTextEditor::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
use Filament\Resources\Pages\CreateRecord;
class CreateBadgeTextEditor extends CreateRecord
{
protected static string $resource = BadgeTextEditorResource::class;
}
@@ -1,51 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
use Filament\Actions\DeleteAction;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use PDOException;
class EditBadgeTextEditor extends EditRecord
{
protected static string $resource = BadgeTextEditorResource::class;
protected function getActions(): array
{
return [DeleteAction::make()];
}
#[\Override]
protected function mutateFormDataBeforeSave(array $data): array
{
return $data;
}
protected function afterSave(): void {}
#[\Override]
protected function handleRecordUpdate(Model $record, array $data): Model
{
try {
return parent::handleRecordUpdate($record, $data);
} catch (PDOException $e) {
if ($e->getCode() === '23000') {
Log::error('Duplicate badge key error: ' . $e->getMessage());
Notification::make()
->title('Duplicate Badge Key')
->body('The badge key already exists. Please use a unique badge key.')
->danger()
->persistent()
->send();
return $record;
}
throw $e;
}
}
}
@@ -1,153 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeTextEditors\Pages;
use App\Filament\Resources\Hotel\BadgeTextEditors\BadgeTextEditorResource;
use App\Models\WebsiteBadge;
use App\Services\SettingsService;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Support\Facades\Log;
class ListBadgeTextEditors extends ListRecords
{
protected static string $resource = BadgeTextEditorResource::class;
protected function getActions(): array
{
return [
CreateAction::make()
->label('Add Badge')
->color('info')
->modalHeading('Add a New Badge')
->modalButton('Create Badge')
->after(function (): void {
Notification::make()
->title('Badge Created')
->body('The badge was successfully created.')
->success()
->send();
}),
Action::make('export')
->label('Export to ExternalTexts')
->action('exportToJson'),
Action::make('backup')
->label('Create Backup of ExternalTexts')
->color('success')
->action('createBackup'),
];
}
public function exportToJson(SettingsService $settingsService)
{
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
if ($jsonPath === '' || $jsonPath === '0') {
Notification::make()
->title('Export Failed')
->body('The JSON file path is not configured in the website settings.')
->danger()
->send();
return;
}
if (! file_exists($jsonPath)) {
Notification::make()
->title('Export Failed')
->body('The JSON file does not exist at the specified path.')
->danger()
->send();
return;
}
$jsonData = json_decode(file_get_contents($jsonPath), true);
$badges = WebsiteBadge::all();
$badgeKeys = $badges->pluck('badge_key')->toArray();
foreach ($jsonData as $key => $value) {
if (
(str_starts_with((string) $key, 'badge_desc_') || str_starts_with((string) $key, 'badge_name_')) &&
! in_array(str_replace(['badge_desc_', 'badge_name_'], '', $key), $badgeKeys)
) {
unset($jsonData[$key]);
}
}
foreach ($badges as $badge) {
$jsonData['badge_desc_' . $badge->badge_key] = $badge->badge_description;
$jsonData['badge_name_' . $badge->badge_key] = $badge->badge_name;
}
try {
$result = file_put_contents(
$jsonPath,
json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
);
if ($result === false) {
throw new Exception('Failed to write to the JSON file.');
}
Notification::make()
->title('Export Successful')
->body('Badge data exported successfully.')
->success()
->send();
} catch (Exception $e) {
Log::error('Failed to export badge data: ' . $e->getMessage());
Notification::make()
->title('Export Failed')
->body('Failed to export badge data. Please check file permissions or contact your administrator.')
->danger()
->send();
}
}
public function createBackup(SettingsService $settingsService)
{
$jsonPath = $settingsService->getOrDefault('nitro_external_texts_file');
if ($jsonPath === '' || $jsonPath === '0') {
Notification::make()
->title('Backup Failed')
->body('The JSON file path is not configured in the website settings.')
->danger()
->send();
return;
}
if (! file_exists($jsonPath)) {
Notification::make()
->title('Backup Failed')
->body('The JSON file does not exist at the specified path.')
->danger()
->send();
return;
}
$backupPath = dirname($jsonPath) . '/ExternalTexts_' . time() . '.json';
if (copy($jsonPath, $backupPath)) {
Notification::make()
->title('Backup Successful')
->body('A backup of the JSON file has been created: ' . basename($backupPath))
->success()
->send();
} else {
Notification::make()
->title('Backup Failed')
->body('Failed to create a backup of the JSON file.')
->danger()
->send();
}
}
}
@@ -1,69 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeUploads;
use App\Filament\Resources\Hotel\BadgeUploads\Pages\ManageBadgeUploads;
use Filament\Forms\Components\FileUpload;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Storage;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
class BadgeUploadResource extends Resource
{
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-gif';
protected static ?string $label = 'Badge Upload';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
FileUpload::make('badge_file')
->label('Upload Badge')
->disk('local')
->directory(setting('badge_path_filesystem'))
->required()
->getUploadedFileNameForStorageUsing(
fn (TemporaryUploadedFile $file): string => strtolower(str_replace([' ', '-', 'æ', 'ø', 'å'], ['_', '_', 'ae', 'oe', 'aa'], $file->getClientOriginalName())),
),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('filename')
->label('File Name')
->sortable(),
TextColumn::make('path')
->label('File Path'),
])
->filters([]);
}
public static function getPages(): array
{
return [
'index' => ManageBadgeUploads::route('/'),
];
}
public static function getFiles(): array
{
$badgePath = env('BadgePath', 'badges');
$files = Storage::disk('local')->files($badgePath);
return collect($files)->map(fn ($file) => [
'filename' => basename($file),
'path' => $file,
])->toArray();
}
}
@@ -1,48 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\BadgeUploads\Pages;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\Page; // Import the Notification class
class ManageBadgeUploads extends Page implements HasForms
{
use InteractsWithForms;
public $badge_file;
protected static string $resource = \App\Filament\Resources\Hotel\BadgeUploads\BadgeUploadResource::class;
protected string $view = 'filament.pages.manage-badge-uploads';
public function mount(): void
{
$this->form->fill([]);
}
protected function getFormSchema(): array
{
return [
FileUpload::make('badge_file')
->label('Upload Badge')
->disk('badges')
->preserveFilenames()
->acceptedFileTypes(['image/gif'])
->rules(['mimes:gif'])
->required(),
];
}
public function save(): void
{
$this->form->getState();
Notification::make()
->title('Badge uploaded successfully!')
->success()
->send();
}
}
@@ -1,180 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\CatalogPages;
use App\Filament\Resources\Hotel\CatalogPages\Pages\CreateCatalogPage;
use App\Filament\Resources\Hotel\CatalogPages\Pages\EditCatalogPage;
use App\Filament\Resources\Hotel\CatalogPages\Pages\ListCatalogPages;
use App\Filament\Resources\Hotel\CatalogPages\RelationManagers\CatalogItemsRelationManager;
use App\Models\Game\Furniture\CatalogPage;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class CatalogPageResource extends Resource
{
protected static ?string $model = CatalogPage::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
protected static string|\UnitEnum|null $navigationGroup = 'Hotel';
public static string $translateIdentifier = 'catalog-pages';
protected static ?string $slug = 'hotel/catalog-pages';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('parent_id')
->required()
->integer(),
TextInput::make('caption_save')
->required(),
TextInput::make('caption')
->required(),
TextInput::make('page_layout')
->required(),
TextInput::make('icon_color')
->required()
->integer(),
TextInput::make('icon_image')
->required()
->integer(),
TextInput::make('min_rank')
->required()
->integer(),
TextInput::make('order_num')
->required()
->integer(),
TextInput::make('visible')
->required(),
TextInput::make('enabled')
->required(),
TextInput::make('club_only')
->required(),
TextInput::make('vip_only')
->required(),
TextInput::make('page_headline')
->required(),
TextInput::make('page_teaser')
->required(),
TextInput::make('page_special'),
TextInput::make('page_text1'),
TextInput::make('page_text2'),
TextInput::make('page_text_details'),
TextInput::make('page_text_teaser'),
TextInput::make('room_id')
->integer(),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('parent_id'),
TextColumn::make('caption_save'),
TextColumn::make('caption')
->searchable()
->sortable(),
TextColumn::make('page_layout'),
TextColumn::make('icon_color'),
ImageColumn::make('icon_image'),
TextColumn::make('min_rank'),
TextColumn::make('order_num'),
TextColumn::make('visible'),
TextColumn::make('enabled'),
TextColumn::make('club_only'),
TextColumn::make('vip_only'),
TextColumn::make('page_headline'),
TextColumn::make('page_teaser'),
TextColumn::make('page_special'),
TextColumn::make('page_text1'),
TextColumn::make('page_text2'),
TextColumn::make('room_id'),
TextColumn::make('includes'),
])
->filters([
//
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
public static function getPages(): array
{
return [
'index' => ListCatalogPages::route('/'),
'create' => CreateCatalogPage::route('/create'),
'edit' => EditCatalogPage::route('/{record}/edit'),
];
}
#[\Override]
public static function getRelations(): array
{
return [
CatalogItemsRelationManager::class,
];
}
public static function getGloballySearchableAttributes(): array
{
return ['caption'];
}
}
@@ -1,18 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\CatalogPages\Pages;
use App\Filament\Resources\Hotel\CatalogPages\CatalogPageResource;
use Filament\Resources\Pages\CreateRecord;
class CreateCatalogPage extends CreateRecord
{
protected static string $resource = CatalogPageResource::class;
protected function getHeaderActions(): array
{
return [
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\CatalogPages\Pages;
use App\Filament\Resources\Hotel\CatalogPages\CatalogPageResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditCatalogPage extends EditRecord
{
protected static string $resource = CatalogPageResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\CatalogPages\Pages;
use App\Filament\Resources\Hotel\CatalogPages\CatalogPageResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListCatalogPages extends ListRecords
{
protected static string $resource = CatalogPageResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,467 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\CatalogPages\RelationManagers;
use App\Models\Game\Furniture\ItemBase;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class CatalogItemsRelationManager extends RelationManager
{
protected static string $relationship = 'catalogItems';
public function form(Schema $schema): Schema
{
return $schema
->components([
Select::make('item_ids')
->label('Furniture Item')
->relationship(
name: 'itemBase',
titleAttribute: 'item_name',
modifyQueryUsing: fn (Builder $query) => $query->orderBy('item_name'),
)
->searchable()
->required()
->preload()
->createOptionForm([
TextInput::make('sprite_id')
->label('Sprite ID')
->numeric()
->default(0),
TextInput::make('public_name')
->maxLength(56),
TextInput::make('item_name')
->required()
->maxLength(70),
TextInput::make('type')
->default('s')
->maxLength(3),
Grid::make(3)
->schema([
TextInput::make('width')
->numeric()
->default(1),
TextInput::make('length')
->numeric()
->default(1),
TextInput::make('stack_height')
->numeric()
->default(0.00),
]),
Grid::make(3)
->schema([
Toggle::make('allow_stack')
->default(true),
Toggle::make('allow_sit')
->default(false),
Toggle::make('allow_lay')
->default(false),
]),
Grid::make(3)
->schema([
Toggle::make('allow_walk')
->default(false),
Toggle::make('allow_gift')
->default(true),
Toggle::make('allow_trade')
->default(true),
]),
Grid::make(3)
->schema([
Toggle::make('allow_recycle')
->default(false),
Toggle::make('allow_marketplace_sell')
->default(false),
Toggle::make('allow_inventory_stack')
->default(true),
]),
TextInput::make('interaction_type')
->default('default')
->maxLength(500),
Grid::make(2)
->schema([
TextInput::make('interaction_modes_count')
->numeric()
->default(1),
TextInput::make('vending_ids')
->default('0')
->maxLength(255),
]),
Grid::make(2)
->schema([
TextInput::make('multiheight')
->default('0')
->maxLength(50),
TextInput::make('customparams')
->maxLength(256),
]),
Grid::make(2)
->schema([
TextInput::make('effect_id_male')
->numeric()
->default(0),
TextInput::make('effect_id_female')
->numeric()
->default(0),
]),
TextInput::make('clothing_on_walk')
->maxLength(255),
])
->columnSpanFull(),
TextInput::make('catalog_name')
->label('Catalog Name')
->required()
->maxLength(100)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
Grid::make(2)
->schema([
TextInput::make('cost_credits')
->label('Cost Credits')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(3),
TextInput::make('cost_points')
->label('Cost Points')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(0),
]),
Grid::make(2)
->schema([
TextInput::make('points_type')
->label('Points Type')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(0),
TextInput::make('amount')
->label('Amount')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(1),
]),
Grid::make(2)
->schema([
Toggle::make('limited_stack')
->label('Limited Stack')
->dehydrateStateUsing(fn ($state) => $state ? '1' : '0'),
Toggle::make('limited_sells')
->label('Limited Sells')
->dehydrateStateUsing(fn ($state) => $state ? '1' : '0'),
]),
Grid::make(3)
->schema([
TextInput::make('order_number')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(1),
TextInput::make('offer_id')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('song_id')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? '')
->default(0),
]),
Textarea::make('extradata')
->label('Extra Data')
->maxLength(500)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
Grid::make(2)
->schema([
Toggle::make('have_offer')
->label('Have Offer')
->default(true)
->dehydrateStateUsing(fn ($state) => $state ? '1' : '0'),
Toggle::make('club_only')
->label('Club Only')
->default(false)
->dehydrateStateUsing(fn ($state) => $state ? '1' : '0'),
]),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('catalog_name')
->columns([
ImageColumn::make('icon')
->getStateUsing(fn ($record) => url($record->itemBase?->icon()))
->size('25px')
->label('Icon')
->circular(),
TextColumn::make('itemBase.item_name')
->label('Furniture Name')
->sortable()
->searchable(),
TextColumn::make('catalog_name')
->label('Catalog Name')
->sortable()
->searchable(),
TextColumn::make('cost_credits')
->label('Credits')
->sortable(),
TextColumn::make('cost_points')
->label('Points')
->sortable(),
IconColumn::make('limited_stack')
->label('Limited')
->boolean(),
IconColumn::make('club_only')
->label('HC Only')
->boolean(),
TextColumn::make('itemBase.type')
->label('Type')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('itemBase.width')
->label('Width')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('itemBase.length')
->label('Length')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('order_number')
->label('Order')
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('type')
->query(fn (Builder $query, array $data): Builder => empty($data['values'])
? $query
: $query->whereHas('itemBase', function (Builder $query) use ($data): void {
$query->whereIn('type', $data['values']);
}))
->options(
fn () => ItemBase::query()
->select('type')
->distinct()
->orderBy('type')
->pluck('type', 'type')
->toArray(),
)
->multiple()
->searchable()
->preload(),
TernaryFilter::make('club_only')
->label('HC Only'),
TernaryFilter::make('limited_stack')
->label('Limited'),
])
->defaultSort('order_number')
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make()->label('Edit Catalog Item'),
Action::make('editItemBase')
->label('Edit Item base')
->icon('heroicon-m-cube')
->modalWidth('3xl')
->modalHeading('Edit Item Base')
->fillForm(function ($record) {
$itemBase = $record->itemBase;
if (! $itemBase) {
return [];
}
return [
'sprite_id' => $itemBase->sprite_id,
'public_name' => $itemBase->public_name,
'item_name' => $itemBase->item_name,
'type' => $itemBase->type,
'width' => $itemBase->width,
'length' => $itemBase->length,
'stack_height' => $itemBase->stack_height,
'allow_stack' => $itemBase->allow_stack,
'allow_sit' => $itemBase->allow_sit,
'allow_lay' => $itemBase->allow_lay,
'allow_walk' => $itemBase->allow_walk,
'allow_gift' => $itemBase->allow_gift,
'allow_trade' => $itemBase->allow_trade,
'allow_recycle' => $itemBase->allow_recycle,
'allow_marketplace_sell' => $itemBase->allow_marketplace_sell,
'allow_inventory_stack' => $itemBase->allow_inventory_stack,
'interaction_type' => $itemBase->interaction_type,
'interaction_modes_count' => $itemBase->interaction_modes_count,
'vending_ids' => $itemBase->vending_ids,
'multiheight' => $itemBase->multiheight,
'customparams' => $itemBase->customparams,
'effect_id_male' => $itemBase->effect_id_male,
'effect_id_female' => $itemBase->effect_id_female,
'clothing_on_walk' => $itemBase->clothing_on_walk,
];
})
->schema([
TextInput::make('sprite_id')
->label('Sprite ID')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('public_name')
->label('Public Name')
->maxLength(56)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('item_name')
->label('Item Name')
->required()
->maxLength(70),
TextInput::make('type')
->maxLength(3)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
Grid::make(3)
->schema([
TextInput::make('width')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('length')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('stack_height')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
]),
Grid::make(3)
->schema([
Toggle::make('allow_stack'),
Toggle::make('allow_sit'),
Toggle::make('allow_lay'),
]),
Grid::make(3)
->schema([
Toggle::make('allow_walk'),
Toggle::make('allow_gift'),
Toggle::make('allow_trade'),
]),
Grid::make(3)
->schema([
Toggle::make('allow_recycle'),
Toggle::make('allow_marketplace_sell'),
Toggle::make('allow_inventory_stack'),
]),
TextInput::make('interaction_type')
->maxLength(500)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
Grid::make(2)
->schema([
TextInput::make('interaction_modes_count')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('vending_ids')
->maxLength(255)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
]),
Grid::make(2)
->schema([
TextInput::make('multiheight')
->maxLength(50)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('customparams')
->maxLength(256)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
]),
Grid::make(2)
->schema([
TextInput::make('effect_id_male')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
TextInput::make('effect_id_female')
->numeric()
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
]),
TextInput::make('clothing_on_walk')
->maxLength(255)
->nullable()
->dehydrateStateUsing(fn ($state) => $state ?? ''),
])
->action(function (array $data, $record): void {
// Transform any null or empty values to empty strings
$data = collect($data)->map(function ($value) {
if ($value === null || $value === '') {
return '';
}
if (is_bool($value)) {
return $value ? '1' : '0';
}
return $value;
})->toArray();
$record->itemBase->forceFill($data)->save();
})
->visible(fn ($record) => $record->itemBase !== null),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
}
@@ -1,96 +0,0 @@
<?php
namespace App\Filament\Resources\Hotel\ChatlogPrivates;
use App\Filament\Resources\Hotel\ChatlogPrivates\Pages\ManageChatlogPrivates;
use App\Filament\Traits\TranslatableResource;
use App\Models\ChatlogPrivate;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class ChatlogPrivateResource extends Resource
{
use TranslatableResource;
protected static ?string $model = ChatlogPrivate::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
protected static string|\UnitEnum|null $navigationGroup = 'Logs';
public static string $translateIdentifier = 'chatlog-private';
protected static ?string $slug = 'hotel/chatlog-private';
#[\Override]
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('sender')
->disabled()
->formatStateUsing(fn ($record) => $record->sender?->username)
->label(__('filament::resources.inputs.sender')),
TextInput::make('receiver')
->disabled()
->formatStateUsing(fn ($record) => $record->receiver?->username)
->label(__('filament::resources.inputs.receiver')),
Textarea::make('message')
->label(__('filament::resources.inputs.message'))
->columnSpanFull()
->disabled(),
]);
}
#[\Override]
public static function table(Table $table): Table
{
return $table
->defaultSort('timestamp', 'desc')
->columns(self::getTable())
->filters([])
->recordActions([
ViewAction::make(),
])
->toolbarActions([]);
}
public static function getTable(): array
{
return [
TextColumn::make('sender.username')
->label(__('filament::resources.columns.sender'))
->toggleable()
->searchable(isIndividual: true),
TextColumn::make('receiver.username')
->label(__('filament::resources.columns.receiver'))
->toggleable()
->searchable(isIndividual: true),
TextColumn::make('message')
->label(__('filament::resources.columns.message'))
->limit(40)
->searchable(isIndividual: true),
TextColumn::make('timestamp')
->label(__('filament::resources.columns.executed_at'))
->dateTime('Y-m-d H:i')
->toggleable(),
];
}
public static function getPages(): array
{
return [
'index' => ManageChatlogPrivates::route('/'),
];
}
}

Some files were not shown because too many files have changed in this diff Show More