- Panduan Template di bahagian atas, boleh lipat/kembang
- Template Aktif (kiri) bersebelahan Konfigurasi Teks (kanan) — col-lg-6
- Auto-detect portrait/landscape dari naturalWidth/naturalHeight imej
- Portrait: max-height 520px | Landscape: max-height 340px
- Badge orientasi (hijau=Landscape, biru=Portrait) dalam header kad
- Laras tinggi juga untuk pratonton upload form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tab QR: Storage::disk('public')->url() — selaras dengan fix QrCodeService
- Tab Template: guna route preview (controller baca dari private disk)
Storage::url() tanpa disk pada private storage tidak boleh diakses terus
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Storage::put() guna default disk (local/private) menyebabkan fail disimpan
di storage/app/private/public/qrcodes/ tapi URL /storage/qrcodes/... cari
fail di storage/app/public/qrcodes/ melalui symlink — lokasi berbeza.
- QrCodeService: guna disk('public'), path ringkas 'qrcodes/{token}.png'
- View: Storage::disk('public')->url() untuk URL yang betul
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mysqladmin resolve host.docker.internal ke IPv6 dahulu — MySQL Windows
tidak dengar pada IPv6, menyebabkan loop tak berakhir dan 502 pada Nginx.
PHP PDO guna IPv4 terus dan berjaya sambung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Buang service db dan volume dbdata dari compose files
- dev: extra_hosts host.docker.internal:host-gateway → capai MySQL Windows host
- prod: IP terus 172.17.200.16, tiada extra_hosts diperlukan
- .env.docker: DB_HOST=host.docker.internal dengan nota untuk production
- entrypoint.sh: default DB_HOST → host.docker.internal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace is_admin boolean with role enum('super_admin','admin') via migration
- ProgramPolicy: admin program can only view/edit/delete own programs
- EnsureIsAdmin: accepts both roles; EnsureSuperAdmin: super_admin only
- UserController + views: super_admin can manage admin accounts
- Sidebar: user management link & role badge gated on isSuperAdmin()
- Fix Controller base class: add AuthorizesRequests trait
- Fix tests: replace nonAdmin() (invalid enum) with adminProgram() against super_admin-only route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EnsureIsAdmin middleware: gates all admin routes on is_admin flag
- Apply admin middleware to entire admin route group
- Fix questionnaire resource route parameter name mismatch ({set})
- Audit log on questionnaire confirmation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CertificateReadyMail: Mailable with Malay HTML email template
- SendCertificateEmailJob: dispatch per-certificate email, log to email_logs
- Email template: HTML with download link, program details, branding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>