Replace logo with client-supplied artwork; add Nablus workshop registration form
- public/images/logo.png replaced with the client's professionally-designed version, cropped to remove excess background margin (used sitewide via logo.blade.php / app-logo.blade.php / app-logo-icon.blade.php) - New standalone, unlinked registration form at /forms/nablus-workshop for a one-off workshop Libra is co-hosting with Smart Business Center, replicating a ClickUp form the client previously used but with Libra's own branding. Submissions email info@libra.adv.ps and redirect to /about. No nav/footer link, no DB storage, no captcha (PRD v1.3). - Tasks 17 (parent), 17-1 (Mailable + email template), 17-2 (page/route/form) taken through product-definition -> task-readiness -> dev-implement -> dev-qa, all Done. 11 new tests passing; full suite run clean of new regressions (69 pre-existing unrelated failures, documented in docs/tasks/17.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class WorkshopRegistrationMail extends Mailable implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public string $fullName,
|
||||||
|
public string $contactNumber,
|
||||||
|
public ?string $email,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'تسجيل جديد - ورشة عمل ريادايات نابلس',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'emails.workshop-registration.ar',
|
||||||
|
with: [
|
||||||
|
'fullName' => $this->fullName,
|
||||||
|
'contactNumber' => $this->contactNumber,
|
||||||
|
'email' => $this->email,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -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.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.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.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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class WorkshopRegistrationMail extends Mailable implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public string $fullName,
|
||||||
|
public string $contactNumber,
|
||||||
|
public ?string $email,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'تسجيل جديد - ورشة عمل ريادايات نابلس',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'emails.workshop-registration.ar',
|
||||||
|
with: [
|
||||||
|
'fullName' => $this->fullName,
|
||||||
|
'contactNumber' => $this->contactNumber,
|
||||||
|
'email' => $this->email,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
<x-mail::message>
|
||||||
|
<div dir="rtl" style="text-align: right;">
|
||||||
|
# تسجيل جديد في ورشة عمل رياديات نابلس
|
||||||
|
|
||||||
|
تم استلام تسجيل جديد بالبيانات التالية:
|
||||||
|
|
||||||
|
- **الاسم الكامل:** {{ $fullName }}
|
||||||
|
- **رقم التواصل:** {{ $contactNumber }}
|
||||||
|
@if($email)
|
||||||
|
- **البريد الإلكتروني:** {{ $email }}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
مع أطيب التحيات،<br>
|
||||||
|
{{ config('app.name') }}
|
||||||
|
</div>
|
||||||
|
</x-mail::message>
|
||||||
|
```
|
||||||
|
|
||||||
|
This follows the same `<x-mail::message>` + `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.
|
||||||
@@ -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 `<x-navigation />` or `<x-footer />`
|
||||||
|
(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
|
||||||
|
`<head>` include pattern from `resources/views/components/layouts/public.blade.php` (its
|
||||||
|
`@include('partials.head')`) but with a bare body:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
@include('partials.head')
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-[#F4F1EA]" style="font-family: var(--font-arabic)">
|
||||||
|
<main class="min-h-screen flex flex-col items-center px-4 py-10 sm:py-16">
|
||||||
|
<div class="w-full max-w-[640px]">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
@fluxScripts
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Mail\WorkshopRegistrationMail;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new #[Layout('components.layouts.standalone')] class extends Component
|
||||||
|
{
|
||||||
|
public string $fullName = '';
|
||||||
|
|
||||||
|
public string $contactNumber = '';
|
||||||
|
|
||||||
|
public string $email = '';
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$validated = $this->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);
|
||||||
|
}
|
||||||
|
}; ?>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<img
|
||||||
|
src="{{ asset('images/workshop-nablus-flyer.jpeg') }}"
|
||||||
|
alt="ورشة متخصصة: القانون، التسويق الرقمي، وإدارة المتاجر الإلكترونية"
|
||||||
|
class="w-full rounded-lg shadow-[0_2px_8px_rgba(0,0,0,0.1)]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg p-6" style="box-shadow: 0 2px 8px rgba(0,0,0,0.1)">
|
||||||
|
<img src="{{ asset('images/logo.png') }}" alt="LIBRA for Rights" class="h-16 w-auto object-contain mb-4" />
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold mb-2" style="color: #2D322A">تسجيل في ورشة عمل ريادايات نابلس</h1>
|
||||||
|
<p class="mb-4" style="color: #2D322A">تخططين لإطلاق مشروعك الخاص أو تطوير تجارتك الإلكترونية؟ 🚀</p>
|
||||||
|
|
||||||
|
<p class="text-sm leading-relaxed mb-6" style="color: #2D322A">
|
||||||
|
ندعوكم للانضمام إلينا في ورشة عمل متخصصة وشاملة تجمع بين القانون، التسويق الرقمي، وإدارة
|
||||||
|
المتاجر الإلكترونية لبناء مشروعك على أسس متينة وناجحة! 🎯📊💡
|
||||||
|
<br><br>
|
||||||
|
برنامج الورشة:
|
||||||
|
<br>
|
||||||
|
■ ١١:٠٠ – ١١:١٠ | الترحيب والافتتاحية والتعريف بالسوق الفلسطيني — أ. لينا عبد الهادي
|
||||||
|
<br>
|
||||||
|
■ ١١:١٠ – ١١:٤٠ | كيف تبدأ مشروعاً ريادياً — أ. هبة المصري
|
||||||
|
<br>
|
||||||
|
■ ١١:٤٥ – ١٢:٢٠ | أساسيات التسويق الرقمي للمشاريع الريادية عبر وسائل التواصل — أ. تالا حلوب
|
||||||
|
<br>
|
||||||
|
■ ١٢:٢٠ – ١٢:٣٠ استراحة
|
||||||
|
<br>
|
||||||
|
■ ١٢:٣٠ – ١:٠٠ | الحيثيات والإجراءات القانونية الخاصة بالتجارة الإلكترونية — أ. هدى عرموش
|
||||||
|
<br>
|
||||||
|
■ ١:٠٥ – ١:٢٥ | البيع عبر المواقع الإلكترونية مقابل منصات التواصل الاجتماعي والفرق بينهما — أ. أيسر غزال
|
||||||
|
<br><br>
|
||||||
|
📍 المكان: قاعة "ورشة" – برج رفيديا – نابلس
|
||||||
|
<br>
|
||||||
|
الوقت: من الساعة ١١:٠٠ صباحاً حتى ١:٢٥ ظهراً
|
||||||
|
<br>
|
||||||
|
✨ الدعوة عامة لجميع الرياديين/ات وأصحاب المشاريع الناشئة والمهتمين!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form wire:submit="register" class="flex flex-col gap-4">
|
||||||
|
<flux:input
|
||||||
|
wire:model="fullName"
|
||||||
|
label="الاسم الكامل"
|
||||||
|
placeholder="أدخل اسمك الكامل"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:input
|
||||||
|
wire:model="contactNumber"
|
||||||
|
label="رقم التواصل"
|
||||||
|
placeholder="أدخل رقم جوالك"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:input
|
||||||
|
wire:model="email"
|
||||||
|
type="email"
|
||||||
|
label="ايميلك للتواصل"
|
||||||
|
placeholder="أدخل بريدك الإلكتروني"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:button variant="primary" type="submit" class="w-full">
|
||||||
|
إرسال
|
||||||
|
</flux:button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
`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 `<x-navigation` nor `<x-footer` (verify:
|
||||||
|
`curl -s http://libra.test/forms/nablus-workshop | grep -E 'x-navigation|x-footer'` returns
|
||||||
|
no match)
|
||||||
|
- [ ] `resources/views/components/navigation.blade.php` and
|
||||||
|
`resources/views/components/footer.blade.php` contain no reference to
|
||||||
|
`forms.nablus-workshop` or `/forms/nablus-workshop` (verify: `rg 'nablus-workshop'
|
||||||
|
resources/views/components/navigation.blade.php resources/views/components/footer.blade.php`
|
||||||
|
returns no match)
|
||||||
|
- [ ] Submitting the form with `fullName` and `contactNumber` filled in and `email` left blank
|
||||||
|
succeeds: no validation error is raised, `Mail::to('info@libra.adv.ps')->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.
|
||||||
@@ -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.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Task Statuses: Libra
|
# 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 — 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 |
|
| ID | Title | Status |
|
||||||
|----|-------|--------|
|
|----|-------|--------|
|
||||||
@@ -121,3 +123,6 @@
|
|||||||
| 16-4 | 16-4 Goals Section | Done |
|
| 16-4 | 16-4 Goals Section | Done |
|
||||||
| 16-5 | 16-5 Legal Services 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 |
|
| 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 |
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 516 KiB After Width: | Height: | Size: 732 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
@include('partials.head')
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-[#F4F1EA]" style="font-family: var(--font-arabic)">
|
||||||
|
<main class="min-h-screen flex flex-col items-center px-4 py-10 sm:py-16">
|
||||||
|
<div class="w-full max-w-[640px]">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
@fluxScripts
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<x-mail::message>
|
||||||
|
<div dir="rtl" style="text-align: right;">
|
||||||
|
# تسجيل جديد في ورشة عمل رياديات نابلس
|
||||||
|
|
||||||
|
تم استلام تسجيل جديد بالبيانات التالية:
|
||||||
|
|
||||||
|
- **الاسم الكامل:** {{ $fullName }}
|
||||||
|
- **رقم التواصل:** {{ $contactNumber }}
|
||||||
|
@if($email)
|
||||||
|
- **البريد الإلكتروني:** {{ $email }}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
مع أطيب التحيات،<br>
|
||||||
|
{{ config('app.name') }}
|
||||||
|
</div>
|
||||||
|
</x-mail::message>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Mail\WorkshopRegistrationMail;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new #[Layout('components.layouts.standalone')] class extends Component
|
||||||
|
{
|
||||||
|
public string $fullName = '';
|
||||||
|
|
||||||
|
public string $contactNumber = '';
|
||||||
|
|
||||||
|
public string $email = '';
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$validated = $this->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);
|
||||||
|
}
|
||||||
|
}; ?>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<img
|
||||||
|
src="{{ asset('images/workshop-nablus-flyer.jpeg') }}"
|
||||||
|
alt="ورشة متخصصة: القانون، التسويق الرقمي، وإدارة المتاجر الإلكترونية"
|
||||||
|
class="w-full rounded-lg shadow-[0_2px_8px_rgba(0,0,0,0.1)]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg p-6" style="box-shadow: 0 2px 8px rgba(0,0,0,0.1)">
|
||||||
|
<img src="{{ asset('images/logo.png') }}" alt="LIBRA for Rights" class="h-16 w-auto object-contain mb-4" />
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold mb-2" style="color: #2D322A">تسجيل في ورشة عمل ريادايات نابلس</h1>
|
||||||
|
<p class="mb-4" style="color: #2D322A">تخططين لإطلاق مشروعك الخاص أو تطوير تجارتك الإلكترونية؟ 🚀</p>
|
||||||
|
|
||||||
|
<p class="text-sm leading-relaxed mb-6" style="color: #2D322A">
|
||||||
|
ندعوكم للانضمام إلينا في ورشة عمل متخصصة وشاملة تجمع بين القانون، التسويق الرقمي، وإدارة
|
||||||
|
المتاجر الإلكترونية لبناء مشروعك على أسس متينة وناجحة! 🎯📊💡
|
||||||
|
<br><br>
|
||||||
|
برنامج الورشة:
|
||||||
|
<br>
|
||||||
|
■ ١١:٠٠ – ١١:١٠ | الترحيب والافتتاحية والتعريف بالسوق الفلسطيني — أ. لينا عبد الهادي
|
||||||
|
<br>
|
||||||
|
■ ١١:١٠ – ١١:٤٠ | كيف تبدأ مشروعاً ريادياً — أ. هبة المصري
|
||||||
|
<br>
|
||||||
|
■ ١١:٤٥ – ١٢:٢٠ | أساسيات التسويق الرقمي للمشاريع الريادية عبر وسائل التواصل — أ. تالا حلوب
|
||||||
|
<br>
|
||||||
|
■ ١٢:٢٠ – ١٢:٣٠ استراحة
|
||||||
|
<br>
|
||||||
|
■ ١٢:٣٠ – ١:٠٠ | الحيثيات والإجراءات القانونية الخاصة بالتجارة الإلكترونية — أ. هدى عرموش
|
||||||
|
<br>
|
||||||
|
■ ١:٠٥ – ١:٢٥ | البيع عبر المواقع الإلكترونية مقابل منصات التواصل الاجتماعي والفرق بينهما — أ. أيسر غزال
|
||||||
|
<br><br>
|
||||||
|
📍 المكان: قاعة "ورشة" – برج رفيديا – نابلس
|
||||||
|
<br>
|
||||||
|
الوقت: من الساعة ١١:٠٠ صباحاً حتى ١:٢٥ ظهراً
|
||||||
|
<br>
|
||||||
|
✨ الدعوة عامة لجميع الرياديين/ات وأصحاب المشاريع الناشئة والمهتمين!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form wire:submit="register" class="flex flex-col gap-4">
|
||||||
|
<flux:input
|
||||||
|
wire:model="fullName"
|
||||||
|
label="الاسم الكامل"
|
||||||
|
placeholder="أدخل اسمك الكامل"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:input
|
||||||
|
wire:model="contactNumber"
|
||||||
|
label="رقم التواصل"
|
||||||
|
placeholder="أدخل رقم جوالك"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:input
|
||||||
|
wire:model="email"
|
||||||
|
type="email"
|
||||||
|
label="ايميلك للتواصل"
|
||||||
|
placeholder="أدخل بريدك الإلكتروني"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<flux:button variant="primary" type="submit" class="w-full">
|
||||||
|
إرسال
|
||||||
|
</flux:button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -13,6 +13,8 @@ Volt::route('/about', 'pages.about')->name('about');
|
|||||||
Volt::route('/booking', 'pages.booking')->name('booking');
|
Volt::route('/booking', 'pages.booking')->name('booking');
|
||||||
Volt::route('/booking/success', 'pages.booking-success')->name('booking.success');
|
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', 'pages.posts.index')->name('posts.index');
|
||||||
Volt::route('/posts/{post}', 'pages.posts.show')->name('posts.show');
|
Volt::route('/posts/{post}', 'pages.posts.show')->name('posts.show');
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Mail\WorkshopRegistrationMail;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
test('workshop registration email implements ShouldQueue', function () {
|
||||||
|
expect(WorkshopRegistrationMail::class)->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');
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Mail\WorkshopRegistrationMail;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Livewire\Volt\Volt;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Mail::fake();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registration page is publicly reachable without site navigation or footer', function () {
|
||||||
|
$this->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';
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user