diff --git a/app/Mail/WorkshopRegistrationMail.php b/app/Mail/WorkshopRegistrationMail.php new file mode 100644 index 0000000..d769709 --- /dev/null +++ b/app/Mail/WorkshopRegistrationMail.php @@ -0,0 +1,59 @@ + $this->fullName, + 'contactNumber' => $this->contactNumber, + 'email' => $this->email, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/docs/prd.md b/docs/prd.md index 30af7b4..7cf0ead 100644 --- a/docs/prd.md +++ b/docs/prd.md @@ -1105,6 +1105,43 @@ All emails in both Arabic and English based on user preference: | 1.0 | Nov 26, 2025 | Initial PRD creation | Development Team | | 1.1 | Dec 20, 2025 | Added brand identity section, clarified booking rules (1/day, 45min), multiple timelines, account management (deactivate/delete), exports, analytics, Terms/Privacy pages | Development Team | | 1.2 | Jan 3, 2026 | Added guest booking functionality: public booking form at /booking, custom captcha, 1-per-day limit, guest email notifications, admin guest booking management | Development Team | +| 1.3 | Sep 27, 2026 | Added standalone Nablus workshop registration form (unlinked page at /forms/nablus-workshop) — see Update below | Development Team | + +--- + +## Update — Version 1.3 +**Date:** 2026-09-27 + +### New Features Added + +**Nablus workshop registration form.** Libra's client is co-hosting a one-off entrepreneurship +workshop ("ورشة متخصصة: القانون، التسويق الرقمي، وإدارة المتاجر الإلكترونية") on 2026-09-30 with +"Smart Business Center." They previously used a generic ClickUp form to collect registrations and +asked for their own branded version instead. Scope, per Naser: + +- A single standalone page at `/forms/nablus-workshop`, reachable only by direct link (Naser sends + the URL to the client to forward to attendees) — **not** added to the site's header/nav/footer. +- Replicates the ClickUp form's content and 3 fields: **الاسم الكامل** (full name, required), + **رقم التواصل** (contact number, required), **ايميلك للتواصل** (email, optional). +- Page carries the workshop's own copy (title, description, full agenda paragraph — same text as + the event flyer) and uses Libra's real branding: the current logo (`public/images/logo.png`) + and the workshop flyer image (client-supplied, to be added to the project as a static asset). + Arabic RTL only — no English variant needed for this page. + Page is RTL Arabic, standalone (no site nav/footer chrome), matching Libra's Warm Cream / Dark + Forest Green / Warm Gold palette. +- On submit: send an email containing the submitted values to `info@libra.adv.ps`, then redirect + the visitor to the existing `/about` page. No database storage required — this is a short-lived, + few-days-only registration page for one event; emailing the submission is the only durable + record needed. +- No captcha, no admin UI, no listing of past submissions — intentionally minimal scope. + +### Scope Changes + +None to existing features. This is additive only. + +### Open Questions + +None outstanding — all decisions confirmed directly with Naser in the same session. --- diff --git a/docs/tasks/17-1.md b/docs/tasks/17-1.md new file mode 100644 index 0000000..4aca3ad --- /dev/null +++ b/docs/tasks/17-1.md @@ -0,0 +1,128 @@ +--- +id: 17-1 +title: 17-1 Registration mailable and email template +status: Done +date: 2026-09-27 +parent: 17 +--- + +## Description + +Create a Mailable and Arabic markdown email template that, when sent, delivers a workshop +registration submission to `info@libra.adv.ps`. This is a prerequisite for task 17-2, which will +call it from the form's submit handler. + +**File to create:** `app/Mail/WorkshopRegistrationMail.php` + +```php + $this->fullName, + 'contactNumber' => $this->contactNumber, + 'email' => $this->email, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} +``` + +This class deliberately does **not** extend `App\Mail\BaseMailable` — `BaseMailable::getFromName()` +switches on the app locale/from-name for the law firm's own bilingual mail, which doesn't apply +here; this mailable always sends in Arabic with a fixed subject, following the same +`ShouldQueue` + `Queueable`/`SerializesModels` pattern as `App\Mail\GuestBookingSubmittedMail` +(`app/Mail/GuestBookingSubmittedMail.php`) instead. + +**File to create:** `resources/views/emails/workshop-registration/ar.blade.php` + +```blade + +
+# تسجيل جديد في ورشة عمل رياديات نابلس + +تم استلام تسجيل جديد بالبيانات التالية: + +- **الاسم الكامل:** {{ $fullName }} +- **رقم التواصل:** {{ $contactNumber }} +@if($email) +- **البريد الإلكتروني:** {{ $email }} +@endif + +مع أطيب التحيات،
+{{ config('app.name') }} +
+
+``` + +This follows the same `` + `dir="rtl"` wrapper pattern as +`resources/views/emails/booking/guest-submitted/ar.blade.php`. + +No route or controller wiring belongs to this task — task 17-2 imports +`App\Mail\WorkshopRegistrationMail`, constructs it with the submitted field values, and sends it +with `Mail::to('info@libra.adv.ps')->send(...)`. + +## Acceptance Criteria + +- [x] `App\Mail\WorkshopRegistrationMail` exists, extends `Illuminate\Mail\Mailable`, implements + `Illuminate\Contracts\Queue\ShouldQueue`, and its constructor accepts `fullName` (string), + `contactNumber` (string), and `email` (nullable string) +- [x] `resources/views/emails/workshop-registration/ar.blade.php` exists, renders `$fullName` and + `$contactNumber` unconditionally, and renders `$email` only when it is not null/empty +- [x] Sending `new WorkshopRegistrationMail('Test Name', '0599123456', null)` via + `Mail::fake()` in a test produces an email with subject `تسجيل جديد - ورشة عمل ريادايات نابلس` + and body containing "Test Name" and "0599123456", with no email line rendered +- [x] Sending the same mailable with a non-null `email` value produces a body that additionally + contains that email address + +## Sub-tasks + +None. diff --git a/docs/tasks/17-2.md b/docs/tasks/17-2.md new file mode 100644 index 0000000..65e2863 --- /dev/null +++ b/docs/tasks/17-2.md @@ -0,0 +1,215 @@ +--- +id: 17-2 +title: 17-2 Standalone registration page, form, and submit wiring +status: Done +date: 2026-09-27 +parent: 17 +--- + +## Description + +Build the standalone, unlinked Nablus workshop registration page at `/forms/nablus-workshop`. +Depends on task 17-1 (`App\Mail\WorkshopRegistrationMail` must already exist). + +### 1. Route + +**File to modify:** `routes/web.php` + +Add, near the other public `Volt::route` calls (after line 14, the `/booking/success` route), +outside any `auth`/`admin`/`client` middleware group: + +```php +Volt::route('/forms/nablus-workshop', 'pages.forms.nablus-workshop')->name('forms.nablus-workshop'); +``` + +Do **not** add any link to this route anywhere else in the codebase — specifically, do not touch +`resources/views/components/navigation.blade.php`, `resources/views/components/footer.blade.php`, +or any other menu/nav component. The page is reachable only by visiting the URL directly. + +### 2. Standalone layout (no site nav/footer) + +**File to create:** `resources/views/components/layouts/standalone.blade.php` + +A minimal, self-contained HTML layout — it must NOT include `` or `` +(unlike `resources/views/components/layouts/public.blade.php`, which this page intentionally does +not use, since the form must not carry the site's admin/login/booking navigation). Follow the +`` include pattern from `resources/views/components/layouts/public.blade.php` (its +`@include('partials.head')`) but with a bare body: + +```blade + + + + @include('partials.head') + + +
+
+ {{ $slot }} +
+
+ @fluxScripts + + +``` + +### 3. Page component + +**File to create:** `resources/views/livewire/pages/forms/nablus-workshop.blade.php` + +Class-based Volt component (this project's convention — see `resources/views/livewire/pages/booking.blade.php` +for the pattern), using the new standalone layout, with these exact public properties, validation +rules, and submit behavior: + +```php +validate([ + 'fullName' => ['required', 'string', 'max:255'], + 'contactNumber' => ['required', 'string', 'max:50'], + 'email' => ['nullable', 'email', 'max:255'], + ], [ + 'fullName.required' => 'يرجى إدخال الاسم الكامل', + 'contactNumber.required' => 'يرجى إدخال رقم التواصل', + 'email.email' => 'يرجى إدخال بريد إلكتروني صحيح', + ]); + + Mail::to('info@libra.adv.ps')->send(new WorkshopRegistrationMail( + fullName: $validated['fullName'], + contactNumber: $validated['contactNumber'], + email: $validated['email'] !== '' ? $validated['email'] : null, + )); + + $this->redirect(route('about'), navigate: false); + } +}; ?> + +
+ ورشة متخصصة: القانون، التسويق الرقمي، وإدارة المتاجر الإلكترونية + +
+ LIBRA for Rights + +

تسجيل في ورشة عمل ريادايات نابلس

+

تخططين لإطلاق مشروعك الخاص أو تطوير تجارتك الإلكترونية؟ 🚀

+ +

+ ندعوكم للانضمام إلينا في ورشة عمل متخصصة وشاملة تجمع بين القانون، التسويق الرقمي، وإدارة + المتاجر الإلكترونية لبناء مشروعك على أسس متينة وناجحة! 🎯📊💡 +

+ برنامج الورشة: +
+ ■ ١١:٠٠ – ١١:١٠ | الترحيب والافتتاحية والتعريف بالسوق الفلسطيني — أ. لينا عبد الهادي +
+ ■ ١١:١٠ – ١١:٤٠ | كيف تبدأ مشروعاً ريادياً — أ. هبة المصري +
+ ■ ١١:٤٥ – ١٢:٢٠ | أساسيات التسويق الرقمي للمشاريع الريادية عبر وسائل التواصل — أ. تالا حلوب +
+ ■ ١٢:٢٠ – ١٢:٣٠ استراحة +
+ ■ ١٢:٣٠ – ١:٠٠ | الحيثيات والإجراءات القانونية الخاصة بالتجارة الإلكترونية — أ. هدى عرموش +
+ ■ ١:٠٥ – ١:٢٥ | البيع عبر المواقع الإلكترونية مقابل منصات التواصل الاجتماعي والفرق بينهما — أ. أيسر غزال +

+ 📍 المكان: قاعة "ورشة" – برج رفيديا – نابلس +
+ الوقت: من الساعة ١١:٠٠ صباحاً حتى ١:٢٥ ظهراً +
+ ✨ الدعوة عامة لجميع الرياديين/ات وأصحاب المشاريع الناشئة والمهتمين! +

+ +
+ + + + + + + + إرسال + + +
+
+``` + +`flux:input` renders the gold-focus-border / SemiBold-label / required-asterisk styling described +in `docs/ux-design.md` "Forms" automatically (same components already used on `pages.booking`), +so no additional CSS is required beyond the inline colors/classes above (Warm Cream `#F4F1EA` page +background from the standalone layout, Forest Green `#2D322A` text, White card, Warm Gold accents +via Flux's own primary-button styling — matching the existing site palette). + +## Acceptance Criteria + +- [ ] Visiting `/forms/nablus-workshop` returns HTTP 200 and does not require authentication +- [ ] The rendered page contains neither `send(...)` is + called with a `WorkshopRegistrationMail` carrying the submitted `fullName`/`contactNumber` + and a null `email`, and the response redirects to `route('about')` +- [ ] Submitting with `fullName` blank, or `contactNumber` blank, re-renders the form with a + validation error and does not send any mail +- [ ] Submitting with a non-empty but invalid `email` (e.g. `not-an-email`) re-renders the form + with a validation error and does not send any mail +- [ ] Submitting with a valid `email` filled in sends a `WorkshopRegistrationMail` whose `email` + property equals the submitted value + +## Sub-tasks + +None. + +## QA Notes (2026-09-27) + +All 6 acceptance criteria verified: `tests/Feature/Public/NablusWorkshopRegistrationTest.php` +(7 tests, all passing) covers route reachability without auth, absence of +`x-navigation`/`x-footer` markup, absence of `nablus-workshop` references in +`navigation.blade.php`/`footer.blade.php`, valid-submission mail dispatch with null email, +validation failures for blank `fullName`/`contactNumber` and invalid `email` (no mail sent), and +valid-email pass-through. No manual tests required — all criteria are HTTP/route/grep/mail +testable. Regression check: last 5 Done tasks (16-4, 16-5, 16-6, 17-1) touch no files this task +changed; 17-1's own mailable tests re-run clean (4/4). Full-suite gate run as parent 17 rollup: +1723 passed, 69 failed, 7 skipped — all 69 failures pre-existing/unrelated (About page +scholarship section removed 2026-01-11 per 16-6, Card/Accessibility/Admin-export/Availability +tests), none reference this task's files (routes/web.php, standalone layout, nablus-workshop +page). Not fixed here per QA scope. diff --git a/docs/tasks/17.md b/docs/tasks/17.md new file mode 100644 index 0000000..bddb62d --- /dev/null +++ b/docs/tasks/17.md @@ -0,0 +1,32 @@ +--- +id: 17 +title: 17 Nablus Workshop Registration Form +status: Done +date: 2026-09-27 +parent: null +--- + +## Description + +Parent container task for the standalone Nablus workshop registration form (PRD v1.3 update, +Section "Update — Version 1.3"). This task carries no independent implementation — it is done +when both subtasks below are Done. + +## Acceptance Criteria + +- [x] Subtask 17-1 is Done +- [x] Subtask 17-2 is Done + +## Sub-tasks + +- 17-1 Registration mailable and email template +- 17-2 Standalone registration page, form, and submit wiring + +## Completion Note (2026-09-27) + +Both subtasks Done. Full-suite regression gate run as parent rollup: +`php -d memory_limit=4G artisan test | cat` — 1723 passed, 69 failed, 7 skipped. All 69 failures +are pre-existing and unrelated to this parent's changes (About page scholarship section removed +2026-01-11 per 16-6, plus Card/Accessibility/Admin-export/Availability-calendar tests); none +reference routes/web.php, standalone.blade.php, or the nablus-workshop page. Not fixed here — +outside this task's scope. diff --git a/docs/tasks/statuses.md b/docs/tasks/statuses.md index 1ea4124..3d97d98 100644 --- a/docs/tasks/statuses.md +++ b/docs/tasks/statuses.md @@ -1,5 +1,7 @@ # Task Statuses: Libra **Last synced:** 2026-09-27 — all rows marked Done at Naser's instruction (live in production ~1 year) +**Last synced:** 2026-09-27 — added tasks 17, 17-1, 17-2 (Nablus workshop registration form, PRD v1.3) +**Last synced:** 2026-09-27 — 17-2 QA passed (7/7 tests), parent 17 Done; full-suite regression gate: 1723 passed / 69 pre-existing unrelated failures | ID | Title | Status | |----|-------|--------| @@ -121,3 +123,6 @@ | 16-4 | 16-4 Goals Section | Done | | 16-5 | 16-5 Legal Services Section | Done | | 16-6 | 16-6 Scholarship Aspiration Section (REMOVED from production 2026-01-11 — do not re-implement) | Done | +| 17 | 17 Nablus Workshop Registration Form | Done | +| 17-1 | 17-1 Registration mailable and email template | Done | +| 17-2 | 17-2 Standalone registration page, form, and submit wiring | Done | diff --git a/public/images/logo.png b/public/images/logo.png index a035c08..38b09d0 100644 Binary files a/public/images/logo.png and b/public/images/logo.png differ diff --git a/public/images/workshop-nablus-flyer.jpeg b/public/images/workshop-nablus-flyer.jpeg new file mode 100644 index 0000000..d46f876 Binary files /dev/null and b/public/images/workshop-nablus-flyer.jpeg differ diff --git a/resources/views/components/layouts/standalone.blade.php b/resources/views/components/layouts/standalone.blade.php new file mode 100644 index 0000000..9541746 --- /dev/null +++ b/resources/views/components/layouts/standalone.blade.php @@ -0,0 +1,14 @@ + + + + @include('partials.head') + + +
+
+ {{ $slot }} +
+
+ @fluxScripts + + diff --git a/resources/views/emails/workshop-registration/ar.blade.php b/resources/views/emails/workshop-registration/ar.blade.php new file mode 100644 index 0000000..75fd27d --- /dev/null +++ b/resources/views/emails/workshop-registration/ar.blade.php @@ -0,0 +1,16 @@ + +
+# تسجيل جديد في ورشة عمل رياديات نابلس + +تم استلام تسجيل جديد بالبيانات التالية: + +- **الاسم الكامل:** {{ $fullName }} +- **رقم التواصل:** {{ $contactNumber }} +@if($email) +- **البريد الإلكتروني:** {{ $email }} +@endif + +مع أطيب التحيات،
+{{ config('app.name') }} +
+
diff --git a/resources/views/livewire/pages/forms/nablus-workshop.blade.php b/resources/views/livewire/pages/forms/nablus-workshop.blade.php new file mode 100644 index 0000000..4ac8cf5 --- /dev/null +++ b/resources/views/livewire/pages/forms/nablus-workshop.blade.php @@ -0,0 +1,103 @@ +validate([ + 'fullName' => ['required', 'string', 'max:255'], + 'contactNumber' => ['required', 'string', 'max:50'], + 'email' => ['nullable', 'email', 'max:255'], + ], [ + 'fullName.required' => 'يرجى إدخال الاسم الكامل', + 'contactNumber.required' => 'يرجى إدخال رقم التواصل', + 'email.email' => 'يرجى إدخال بريد إلكتروني صحيح', + ]); + + Mail::to('info@libra.adv.ps')->send(new WorkshopRegistrationMail( + fullName: $validated['fullName'], + contactNumber: $validated['contactNumber'], + email: $validated['email'] !== '' ? $validated['email'] : null, + )); + + $this->redirect(route('about'), navigate: false); + } +}; ?> + +
+ ورشة متخصصة: القانون، التسويق الرقمي، وإدارة المتاجر الإلكترونية + +
+ LIBRA for Rights + +

تسجيل في ورشة عمل ريادايات نابلس

+

تخططين لإطلاق مشروعك الخاص أو تطوير تجارتك الإلكترونية؟ 🚀

+ +

+ ندعوكم للانضمام إلينا في ورشة عمل متخصصة وشاملة تجمع بين القانون، التسويق الرقمي، وإدارة + المتاجر الإلكترونية لبناء مشروعك على أسس متينة وناجحة! 🎯📊💡 +

+ برنامج الورشة: +
+ ■ ١١:٠٠ – ١١:١٠ | الترحيب والافتتاحية والتعريف بالسوق الفلسطيني — أ. لينا عبد الهادي +
+ ■ ١١:١٠ – ١١:٤٠ | كيف تبدأ مشروعاً ريادياً — أ. هبة المصري +
+ ■ ١١:٤٥ – ١٢:٢٠ | أساسيات التسويق الرقمي للمشاريع الريادية عبر وسائل التواصل — أ. تالا حلوب +
+ ■ ١٢:٢٠ – ١٢:٣٠ استراحة +
+ ■ ١٢:٣٠ – ١:٠٠ | الحيثيات والإجراءات القانونية الخاصة بالتجارة الإلكترونية — أ. هدى عرموش +
+ ■ ١:٠٥ – ١:٢٥ | البيع عبر المواقع الإلكترونية مقابل منصات التواصل الاجتماعي والفرق بينهما — أ. أيسر غزال +

+ 📍 المكان: قاعة "ورشة" – برج رفيديا – نابلس +
+ الوقت: من الساعة ١١:٠٠ صباحاً حتى ١:٢٥ ظهراً +
+ ✨ الدعوة عامة لجميع الرياديين/ات وأصحاب المشاريع الناشئة والمهتمين! +

+ +
+ + + + + + + + إرسال + + +
+
diff --git a/routes/web.php b/routes/web.php index df37439..0050325 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,8 @@ Volt::route('/about', 'pages.about')->name('about'); Volt::route('/booking', 'pages.booking')->name('booking'); Volt::route('/booking/success', 'pages.booking-success')->name('booking.success'); +Volt::route('/forms/nablus-workshop', 'pages.forms.nablus-workshop')->name('forms.nablus-workshop'); + Volt::route('/posts', 'pages.posts.index')->name('posts.index'); Volt::route('/posts/{post}', 'pages.posts.show')->name('posts.show'); diff --git a/tests/Feature/Mail/WorkshopRegistrationMailTest.php b/tests/Feature/Mail/WorkshopRegistrationMailTest.php new file mode 100644 index 0000000..cb89872 --- /dev/null +++ b/tests/Feature/Mail/WorkshopRegistrationMailTest.php @@ -0,0 +1,47 @@ +toImplement(ShouldQueue::class); +}); + +test('workshop registration email has correct arabic subject', function () { + $mailable = new WorkshopRegistrationMail('Test Name', '0599123456', null); + + expect($mailable->envelope()->subject) + ->toBe('تسجيل جديد - ورشة عمل ريادايات نابلس'); +}); + +test('workshop registration email renders full name and contact number without email line', function () { + Mail::fake(); + + $mailable = new WorkshopRegistrationMail('Test Name', '0599123456', null); + Mail::to('info@libra.adv.ps')->send($mailable); + + $rendered = $mailable->render(); + + expect($rendered) + ->toContain('Test Name') + ->toContain('0599123456'); + + Mail::assertQueued(WorkshopRegistrationMail::class, function ($mail) { + return ! str_contains($mail->render(), 'البريد الإلكتروني'); + }); +}); + +test('workshop registration email renders email address when provided', function () { + Mail::fake(); + + $mailable = new WorkshopRegistrationMail('Test Name', '0599123456', 'test@example.com'); + Mail::to('info@libra.adv.ps')->send($mailable); + + $rendered = $mailable->render(); + + expect($rendered) + ->toContain('Test Name') + ->toContain('0599123456') + ->toContain('test@example.com'); +}); diff --git a/tests/Feature/Public/NablusWorkshopRegistrationTest.php b/tests/Feature/Public/NablusWorkshopRegistrationTest.php new file mode 100644 index 0000000..6ac280e --- /dev/null +++ b/tests/Feature/Public/NablusWorkshopRegistrationTest.php @@ -0,0 +1,87 @@ +get('/forms/nablus-workshop') + ->assertOk() + ->assertDontSee('x-navigation', false) + ->assertDontSee('x-footer', false); +}); + +test('navigation and footer components do not link to the workshop page', function () { + $navigation = file_get_contents(resource_path('views/components/navigation.blade.php')); + $footer = file_get_contents(resource_path('views/components/footer.blade.php')); + + expect($navigation)->not->toContain('nablus-workshop'); + expect($footer)->not->toContain('nablus-workshop'); +}); + +test('valid submission with blank email sends workshop registration mail and redirects to about', function () { + Volt::test('pages.forms.nablus-workshop') + ->set('fullName', 'سارة أحمد') + ->set('contactNumber', '0599123456') + ->set('email', '') + ->call('register') + ->assertHasNoErrors() + ->assertRedirect(route('about')); + + Mail::assertQueued(WorkshopRegistrationMail::class, function ($mail) { + return $mail->fullName === 'سارة أحمد' + && $mail->contactNumber === '0599123456' + && $mail->email === null; + }); +}); + +test('blank full name fails validation and does not send mail', function () { + Volt::test('pages.forms.nablus-workshop') + ->set('fullName', '') + ->set('contactNumber', '0599123456') + ->call('register') + ->assertHasErrors(['fullName']); + + Mail::assertNothingSent(); + Mail::assertNothingQueued(); +}); + +test('blank contact number fails validation and does not send mail', function () { + Volt::test('pages.forms.nablus-workshop') + ->set('fullName', 'سارة أحمد') + ->set('contactNumber', '') + ->call('register') + ->assertHasErrors(['contactNumber']); + + Mail::assertNothingSent(); + Mail::assertNothingQueued(); +}); + +test('invalid email fails validation and does not send mail', function () { + Volt::test('pages.forms.nablus-workshop') + ->set('fullName', 'سارة أحمد') + ->set('contactNumber', '0599123456') + ->set('email', 'not-an-email') + ->call('register') + ->assertHasErrors(['email']); + + Mail::assertNothingSent(); + Mail::assertNothingQueued(); +}); + +test('valid email is passed through to the mailable', function () { + Volt::test('pages.forms.nablus-workshop') + ->set('fullName', 'سارة أحمد') + ->set('contactNumber', '0599123456') + ->set('email', 'sara@example.com') + ->call('register') + ->assertHasNoErrors(); + + Mail::assertQueued(WorkshopRegistrationMail::class, function ($mail) { + return $mail->email === 'sara@example.com'; + }); +});