new updates

This commit is contained in:
2026-05-25 17:41:07 +02:00
parent ea63897455
commit 025f097b68
713 changed files with 71578 additions and 1958 deletions

View File

@@ -18,6 +18,12 @@ Use `search-docs` for detailed Pest 4 patterns and documentation.
All tests must be written using Pest. Use `php artisan make:test --pest {name}`.
The `{name}` argument should include only the path and test name, but should not include the test suite.
- Incorrect: `php artisan make:test --pest Feature/SomeFeatureTest` will generate `tests/Feature/Feature/SomeFeatureTest.php`
- Correct: `php artisan make:test --pest SomeControllerTest` will generate `tests/Feature/SomeControllerTest.php`
- Incorrect: `php artisan make:test --pest --unit Unit/SomeServiceTest` will generate `tests/Unit/Unit/SomeServiceTest.php`
- Correct: `php artisan make:test --pest --unit SomeServiceTest` will generate `tests/Unit/SomeServiceTest.php`
### Test Organization
- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
@@ -156,4 +162,5 @@ arch('controllers')
- Using `assertStatus(200)` instead of `assertSuccessful()`
- Forgetting datasets for repetitive validation tests
- Deleting tests without approval
- Forgetting `assertNoJavaScriptErrors()` in browser tests
- Forgetting `assertNoJavaScriptErrors()` in browser tests
- Prefixing `Feature/` or `Unit/` in `{name}` when using `make:test`

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ _ide_helper.php
Homestead.json
Homestead.yaml
Thumbs.db
.DS_Store
*.swp

View File

@@ -23,11 +23,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
## Skills Activation
This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.
- `laravel-best-practices` — Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns.
- `pest-testing` — Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code.
- `tailwindcss-development` — Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS.
This project has domain-specific skills available in `**/skills/**`. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.
## Conventions
@@ -161,6 +157,7 @@ This project has domain-specific skills available. You MUST activate the relevan
## Pest
- This project uses Pest for testing. Create tests: `php artisan make:test --pest {name}`.
- The `{name}` argument should not include the test suite directory. Use `php artisan make:test --pest SomeFeatureTest` instead of `php artisan make:test --pest Feature/SomeFeatureTest`.
- Run tests: `php artisan test --compact` or filter: `php artisan test --compact --filter=testName`.
- Do NOT delete tests without approval.

View File

@@ -3,9 +3,11 @@
namespace App\Http\Controllers;
use App\Http\Requests\StoreContactMessageRequest;
use App\Mail\ContactFormSubmission;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Klevze\ControlPanel\Facades\ActiveLanguage;
@@ -32,14 +34,33 @@ class PageController extends Controller
public function about(): View
{
return $this->render('pages.about', 'About Us', 'about');
$aboutPage = DB::table('contents')
->where('content_id', 4)
->exists()
? app(CmsPage::class)->load(4, app()->getLocale())
: null;
$seo = $this->pageSeo($aboutPage, 'About Us');
return $this->render('pages.about', $seo['title'], 'about', [
'seo' => $seo,
]);
}
public function work(): View
{
$workPage = DB::table('contents')
->where('content_id', 3)
->exists()
? app(CmsPage::class)->load(3, app()->getLocale())
: null;
$seo = $this->pageSeo($workPage, 'Works');
$categoryMap = $this->projectCategoryMap();
return $this->render('pages.work', 'Works', 'work', [
return $this->render('pages.work', $seo['title'], 'work', [
'seo' => $seo,
'artworks' => $this->portfolioProjects($categoryMap),
'categories' => array_values($categoryMap),
]);
@@ -74,12 +95,34 @@ class PageController extends Controller
public function contact(): View
{
return $this->render('pages.contact', 'Contact Us', 'contact');
session(['contact_form_started_at' => now()->timestamp]);
$contactPage = DB::table('contents')
->where('content_id', 6)
->exists()
? app(CmsPage::class)->load(6, app()->getLocale())
: null;
$seo = $this->pageSeo($contactPage, 'Contact Us');
return $this->render('pages.contact', $seo['title'], 'contact', [
'seo' => $seo,
]);
}
public function terms(): View
{
return $this->render('pages.terms', 'Terms', 'terms');
$termsPage = DB::table('contents')
->where('content_id', 1)
->exists()
? app(CmsPage::class)->load(1, app()->getLocale())
: null;
$seo = $this->pageSeo($termsPage, 'Terms');
return $this->render('pages.terms', $seo['title'], 'terms', [
'seo' => $seo,
]);
}
public function thankyou(): View
@@ -89,9 +132,15 @@ class PageController extends Controller
public function submitContact(StoreContactMessageRequest $request): RedirectResponse
{
Log::info('Website contact form submission', $request->validated());
$contactDetails = $request->validated();
return redirect()->route('thankyou');
Log::info('Website contact form submission', $contactDetails);
Mail::to('office@aritmija.si')->send(new ContactFormSubmission($contactDetails));
$request->session()->forget('contact_form_started_at');
return redirect()->route('thankyou', ['locale' => app()->getLocale()]);
}
private function render(string $view, string $title, string $page, array $data = []): View

View File

@@ -3,9 +3,13 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Validator;
class StoreContactMessageRequest extends FormRequest
{
private const MIN_SUBMIT_SECONDS = 3;
public function authorize(): bool
{
return true;
@@ -17,6 +21,52 @@ class StoreContactMessageRequest extends FormRequest
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255'],
'message' => ['required', 'string', 'max:2000'],
'website' => ['nullable', 'string', 'max:0'],
];
}
public function after(): array
{
return [function (Validator $validator): void {
if ($this->submittedTooQuickly()) {
$validator->errors()->add('form', 'Please wait a moment and try again.');
}
if ($this->containsSpamLinks()) {
$validator->errors()->add('message', 'Please remove promotional links and try again.');
}
if ($this->nameContainsUrl()) {
$validator->errors()->add('name', 'Please enter a valid name.');
}
}];
}
private function submittedTooQuickly(): bool
{
$startedAt = (int) $this->session()->get('contact_form_started_at', 0);
if ($startedAt <= 0) {
return true;
}
return now()->timestamp - $startedAt < self::MIN_SUBMIT_SECONDS;
}
private function containsSpamLinks(): bool
{
return preg_match_all('/https?:\/\/|www\./i', (string) $this->input('message', '')) > 1;
}
private function nameContainsUrl(): bool
{
return Str::contains(Str::lower((string) $this->input('name', '')), ['http://', 'https://', 'www.']);
}
public function messages(): array
{
return [
'website.max' => 'Please leave this field empty.',
];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class ContactFormSubmission extends Mailable
{
use Queueable;
public function __construct(public array $contactDetails)
{
}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Website contact form submission',
replyTo: [
new Address(
$this->contactDetails['email'],
$this->contactDetails['name'],
),
],
);
}
public function content(): Content
{
return new Content(
view: 'emails.contact-form-submission',
);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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