commit 38295e7e88fd67fdc30370ca13616ca21614736e Author: marco Date: Mon Sep 7 19:06:53 2026 -0300 feat(cms): implement complete SysSiteCMS content management system Initial implementation of the HomeSysPanel CMS built with Laravel 13.0. Includes complete admin panel, content management, user authentication, modular architecture for products/services/chatbot, and marketing tools. Key features: - User authentication with email verification and password reset - Role-based access control (Admin/User with granular permissions) - Content management (pages, news, FAQ, downloads, galleries, banners) - Contact management system with subjects and responses - Marketing tools (email campaigns, visitor analytics, social sharing) - Modular architecture (Products, Services, Chatbot modules) - AI Chatbot integration with OpenRouter.ai and LlamaCpp support - Visitor tracking with geolocation - SEO optimization with meta tags and Open Graph - Responsive design with Bootstrap 5 - Dark mode support - Maintenance mode - Feature request system diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..a186cd2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..fcb21d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.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 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..b71b1ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.fleet +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +Homestead.json +Homestead.yaml +Thumbs.db diff --git a/EULA.md b/EULA.md new file mode 100755 index 0000000..6159d9a --- /dev/null +++ b/EULA.md @@ -0,0 +1,46 @@ +EULA – Contrato de Licença de Uso de Software +SysSiteCMS – Syspanel.com.br +Última atualização: 2026 +Este Contrato de Licença de Uso de Software ("EULA") é um acordo legal entre o Licenciante (Syspanel.com.br) e o Cliente ("Licenciado") para uso do software denominado SysSiteCMS . +Ao utilizar o software, o Licenciado concorda com os termos deste contrato. +1. Concessão de Licença +O Licenciante concede ao Cliente uma licença de uso não exclusiva, perpétua, intransferível e não sublicenciável do software. +Esta licença não constitui venda do software , mas apenas a concessão de direito de uso conforme os termos aqui descritos. +2. Direitos Concedidos ao Cliente +O Cliente está autorizado a: +Utilizar o software sem limitação de usuários dentro de sua própria organização; +Acessar o código-fonte do software; +Modificar o software para atender às suas necessidades internas; +Manter versões modificadas para uso próprio. +3. Restrições +O Cliente não está autorizado a: +Revender, sublicenciar, alugar ou comercializar o software; +Distribuir, compartilhar ou disponibilizar o software a terceiros; +Utilizar o software como base para criação de produtos ou serviços destinados à venda; +Oferecer o software como serviço (SaaS, hospedado ou similar) para terceiros; +Transferir o software para outras empresas, mesmo que do mesmo grupo econômico, sem autorização prévia do Licenciante. +4. Propriedade Intelectual +A titularidade do software, incluindo seu código-fonte original, arquitetura, estrutura e componentes reutilizáveis, permanece exclusivamente com o Licenciante. +As modificações realizadas pelo Cliente não transferem nem alteram a propriedade do software original. +5. Componentes de Terceiros +Este software pode incluir componentes de terceiros, incluindo, mas não se limitando ao framework Laravel, licenciado sob a MIT License. +Tais componentes são de propriedade de seus respectivos autores e estão sujeitos às suas próprias licenças. +O Licenciado concorda em cumprir os termos dessas licenças de terceiros. +6. Suporte e Atualizações +Salvo acordo específico entre as partes, o Licenciante não está obrigado a fornecer suporte técnico, manutenção ou atualizações do software. +7. Garantias +O software é fornecido "no estado em que se encontra" ("as is"), sem garantias de qualquer tipo, expressas ou implícitas, incluindo, mas não se limitando a garantias de desempenho, adequação a um propósito específico ou não violação de direitos. +8. Limitação de Responsabilidade +Em nenhuma hipótese o Licenciante será responsável por quaisquer danos diretos, indiretos, incidentais ou consequenciais decorrentes do uso ou da incapacidade de uso do software. +9. Violação e Rescisão +O descumprimento de qualquer cláusula deste contrato poderá resultar na revogação imediata da licença concedida, sem prejuízo das medidas legais cabíveis. +10. Lei Aplicável +Este contrato será regido pelas leis da República Federativa do Brasil. +11. Disposições Gerais +Este contrato constitui o acordo completo entre as partes; +Qualquer modificação deverá ser feita por escrito; +A eventual tolerância de uma das partes não implica renúncia de direitos. +12. Contato +Para dúvidas, solicitações ou permissões especiais: +Syspanel.com.br +contato@syspanel.com.br diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..ec2483f --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,47 @@ +# LICENSE + +Copyright (c) 2026 Marco Costa + +Email: contato@syspanel.com.br + +This software is proprietary and confidential. All rights reserved. + +## Proprietary License + +Redistribution and use in source and binary forms, with or without modification, +are strictly prohibited without prior written permission from the copyright holder. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +## Third-Party Licenses + +This project includes third-party software under their respective licenses: + +### Laravel Framework +- License: MIT License +- Copyright: Taylor Otwell +- Repository: https://github.com/laravel/framework + +### Bootstrap Icons +- License: MIT License +- Copyright: The Bootstrap Authors +- Repository: https://github.com/twbs/icons + +## Contact + +For licensing inquiries, please contact: +- Email: contato@syspanel.com.br +- Website: https://syspanel.com.br + +--- +Last Updated: May 2026 diff --git a/README.md b/README.md new file mode 100755 index 0000000..88533c6 --- /dev/null +++ b/README.md @@ -0,0 +1,248 @@ +# HomeSysPanel - SysSiteCMS CMS + +A complete Content Management System (CMS) built with Laravel 13.0 for managing business content, products, services, news, and marketing campaigns. + +## Features + +- **Content Management**: Full CRUD for pages, products, services, galleries, news, FAQs, and downloads +- **News Module**: Complete news system with featured image, additional images gallery, author, publish date, active/deactivated status, SEO fields, and slug generation +- **Gallery System**: Image galleries organized by categories with multi-image upload support +- **User Management**: Multi-level authentication (Admin/Client) with email verification and permission-based access +- **Marketing Tools**: Visitor analytics, email marketing, and promotional features +- **Contact System**: Advanced contact form with subjects and response management +- **SEO Optimization**: Built-in SEO fields for pages, news, and global settings +- **Visitor Tracking**: Real-time visitor analytics with geolocation +- **Responsive Design**: Mobile-friendly interface for both public and admin areas +- **Modular Architecture**: Products, Services, and Chatbot modules can be enabled/disabled independently +- **AI Chatbot**: Intelligent customer service chatbot with OpenRouter.ai integration, knowledge base management, and conversation tracking + +## Requirements + +- PHP 8.3 or higher +- MySQL 8.0 / MariaDB 10.8+ / SQLite +- Redis (optional, for caching) +- Composer +- NPM + +## Installation + +### Clone the repository + +```bash +git clone +cd app +``` + +### Install dependencies + +```bash +composer install +npm install +``` + +### Environment setup + +```bash +cp .env.example .env +php artisan key:generate +``` + +### Configure database + +Edit `.env` file: + +```env +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_DATABASE=homesyspanel +DB_USERNAME=root +DB_PASSWORD=your_password +``` + +### Run migrations + +```bash +php artisan migrate --seed +``` + +### Build assets + +```bash +npm run build +``` + +### Start development server + +```bash +php artisan serve +``` + +Access the application at `http://localhost:8000` + +## Default Credentials + +- **Email**: admin@syspanel.com.br +- **Password**: password + +**Important**: Change the default password after first login. + +## Documentation + +- **Usage Manual**: [MANUAL_DE_USO_SYSSITE.md](MANUAL_DE_USO_SYSSITE.md) +- **Module System**: [MODULE_SYSTEM.md](MODULE_SYSTEM.md) +- **Skills and MCP Guide**: [SKILLS_AND_MCP_GUIDE.md](SKILLS_AND_MCP_GUIDE.md) +- **AI Coder Module Guide**: [AI_CODER_MODULE_GUIDE.md](AI_CODER_MODULE_GUIDE.md) + +## Project Structure + +``` +app/ +├── app/ +│ ├── Http/ +│ │ ├── Controllers/ # Application controllers +│ │ │ └── Admin/ # Admin panel controllers +│ │ └── Middleware/ # Custom middleware +│ ├── Models/ # Eloquent models +│ ├── Modules/ # Modular features (Products, Services) +│ │ ├── Products/ # Product catalog module +│ │ └── Services/ # Services catalog module +│ ├── Mail/ # Email classes +│ ├── Notifications/ # Notification classes +│ ├── Providers/ # Service providers +│ └── Support/ # Helpers and utility classes +├── config/ # Configuration files +├── database/ +│ ├── migrations/ # Database migrations +│ └── seeders/ # Database seeders +├── resources/ +│ └── views/ # Blade templates +├── routes/ # Route definitions +└── public/ # Public assets +``` + +## Database Schema + +The application uses the following main tables: + +- `users` - User accounts with type-based permissions +- `user_types` - User type definitions +- `products` - Product catalog (module) +- `product_categories` - Product categories (module) +- `services` - Services offered (module) +- `news` - News articles with SEO fields +- `news_images` - Additional images for news articles +- `galleries` - Image galleries +- `gallery_images` - Images within galleries +- `gallery_categories` - Gallery category organization +- `banners` - Homepage banners +- `pages` - Institutional pages with SEO +- `faqs` - Frequently asked questions +- `downloads` - Downloadable files +- `contact_messages` - Contact form submissions +- `contact_responses` - Responses to contact messages +- `contact_subjects` - Contact form subjects +- `visitor_logs` - Visitor tracking data +- `settings` - Global configuration +- `chatbot_knowledge` - Chatbot knowledge base entries +- `chatbot_conversations` - Chatbot conversation sessions +- `chatbot_messages` - Individual chatbot messages + +## Permissions System + +The admin panel uses a permission-based access control system. Available permissions: + +| Permission | Description | +|------------|-------------| +| `pages` | Manage institutional pages | +| `news` | Manage news articles | +| `galleries` | Manage galleries and gallery categories | +| `products` | Manage products and categories (module) | +| `services` | Manage services (module) | +| `contacts` | Manage contact messages | +| `subjects` | Manage contact subjects | +| `faqs` | Manage FAQs | +| `downloads` | Manage downloads | +| `marketing` | Access marketing tools, analytics, banners | +| `chatbot` | Manage chatbot knowledge base and conversations (module) | +| `settings` | Manage global settings | + +## Public Routes + +| Route | Description | +|-------|-------------| +| `/` | Homepage | +| `/sobre` | About page | +| `/noticias` | News listing | +| `/noticias/{slug}` | News article detail | +| `/galerias/categoria/{slug}` | Galleries by category | +| `/galeria/{slug}` | Gallery detail | +| `/faq` | FAQ page | +| `/contato` | Contact form | +| `/downloads` | Downloads page | +| `/politica-privacidade` | Privacy policy | +| `/produtos` | Products catalog (module) | +| `/servicos` | Services catalog (module) | +| `/chatbot/*` | Chatbot API endpoints (module) | + +## UI Libraries + +- **Admin Panel**: SB Admin 2 (Bootstrap 4 + jQuery) +- **Public Pages**: Bootstrap 5.3 +- **Icons**: Bootstrap Icons (MIT License) + +## Development + +### Running tests + +```bash +php artisan test +``` + +### Code style + +```bash +./vendor/bin/pint +``` + +### Clear cache + +```bash +php artisan cache:clear +php artisan config:clear +php artisan route:clear +php artisan view:clear +``` + +### Queue worker + +```bash +php artisan queue:work +``` + +## Security + +- All public forms include CSRF protection +- All public forms include CAPTCHA verification +- Permission-based access control for admin features +- Always keep dependencies updated +- Use HTTPS in production +- Set `APP_DEBUG=false` in production +- Configure proper file permissions +- Use strong passwords + +## Support + +For support and inquiries: +- Email: contato@syspanel.com.br +- Website: https://syspanel.com.br + +## License + +This software is proprietary. See [LICENSE.md](LICENSE.md) for details. + +--- + +**Version**: 1.2 +**Framework**: Laravel 13.0 +**PHP**: 8.3+ +**Last Updated**: May 2026 diff --git a/TERMOS_DE_USO_DO_SISTEMA.md b/TERMOS_DE_USO_DO_SISTEMA.md new file mode 100755 index 0000000..9399e3f --- /dev/null +++ b/TERMOS_DE_USO_DO_SISTEMA.md @@ -0,0 +1,9 @@ +# Termo de Uso do Sistema – SysSiteCMS + +O cliente pode utilizar o sistema livremente dentro de sua empresa, sem limitação de usuários. + +O cliente terá acesso ao código-fonte e poderá realizar modificações conforme necessário para seu uso interno. + +O sistema não poderá ser revendido, distribuído ou utilizado como produto ou serviço para terceiros. + +O uso do sistema implica concordância com os termos acima. diff --git a/app/Http/Controllers/Admin/BannerController.php b/app/Http/Controllers/Admin/BannerController.php new file mode 100755 index 0000000..1fedf05 --- /dev/null +++ b/app/Http/Controllers/Admin/BannerController.php @@ -0,0 +1,89 @@ +paginate(10); + return view('admin.banners.index', compact('banners')); + } + + public function create() + { + return view('admin.banners.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'image' => 'required|image|max:2048|mimes:jpeg,png,jpg,gif,webp', + 'link' => 'nullable|string|max:500|regex:/^https?:\/\/.+\..+$/i', + 'button_text' => 'nullable|string|max:50', + 'order' => 'required|integer', + 'active' => 'required|boolean', + ], [ + 'image.mimes' => 'A imagem deve ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + 'link.regex' => 'O link deve começar com http:// ou https:// e ter um formato válido.', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + $data['image'] = $request->file('image')->store('banners', 'public'); + } + + Banner::create($data); + + return redirect()->route('admin.marketing.banners.index')->with('success', 'Banner criado com sucesso.'); + } + + public function edit(Banner $banner) + { + return view('admin.banners.edit', compact('banner')); + } + + public function update(Request $request, Banner $banner) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'image' => 'nullable|image|max:2048|mimes:jpeg,png,jpg,gif,webp', + 'link' => 'nullable|string|max:500|regex:/^https?:\/\/.+\..+$/i', + 'button_text' => 'nullable|string|max:50', + 'order' => 'required|integer', + 'active' => 'required|boolean', + ], [ + 'image.mimes' => 'A imagem deve ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + 'link.regex' => 'O link deve começar com http:// ou https:// e ter um formato válido.', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + if ($banner->image) { + Storage::disk('public')->delete($banner->image); + } + $data['image'] = $request->file('image')->store('banners', 'public'); + } + + $banner->update($data); + + return redirect()->route('admin.marketing.banners.index')->with('success', 'Banner atualizado com sucesso.'); + } + + public function destroy(Banner $banner) + { + if ($banner->image) { + Storage::disk('public')->delete($banner->image); + } + $banner->delete(); + return redirect()->route('admin.marketing.banners.index')->with('success', 'Banner excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/ContactController.php b/app/Http/Controllers/Admin/ContactController.php new file mode 100755 index 0000000..4831f3f --- /dev/null +++ b/app/Http/Controllers/Admin/ContactController.php @@ -0,0 +1,52 @@ +latest()->paginate(10); + return view('admin.contacts.index', compact('contacts')); + } + + public function show(ContactMessage $contact) + { + $contact->update(['read' => true]); + $contact->load('responses.user', 'subject'); + return view('admin.contacts.show', compact('contact')); + } + + public function reply(Request $request, ContactMessage $contact) + { + $request->validate([ + 'message' => 'required|string|min:10', + ]); + + // 1. Salvar no Banco de Dados + ContactResponse::create([ + 'contact_message_id' => $contact->id, + 'user_id' => Auth::id(), + 'message' => $request->message, + ]); + + // 2. Enviar E-mail + Mail::to($contact->email)->send(new ContactReplyMail($contact, $request->message)); + + return redirect()->route('admin.contacts.show', $contact)->with('success', 'Resposta enviada e registrada com sucesso!'); + } + + public function destroy(ContactMessage $contact) + { + $contact->delete(); + return redirect()->route('admin.contacts.index')->with('success', 'Mensagem excluída com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/DownloadController.php b/app/Http/Controllers/Admin/DownloadController.php new file mode 100755 index 0000000..7fa523b --- /dev/null +++ b/app/Http/Controllers/Admin/DownloadController.php @@ -0,0 +1,88 @@ +paginate(10); + return view('admin.downloads.index', compact('downloads')); + } + + public function create() + { + return view('admin.downloads.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'file' => 'required|file|max:20480|mimes:pdf,doc,docx,zip,rar,txt,xls,xlsx', // Máx 20MB + 'file_type' => 'required|string|in:Driver,Manual,Software,Outro', + ], [ + 'file.mimes' => 'O arquivo deve ser do tipo: PDF, DOC, DOCX, ZIP, RAR, TXT, XLS ou XLSX.', + ]); + + $path = $request->file('file')->store('downloads', 'public'); + + Download::create([ + 'title' => $request->title, + 'description' => $request->description, + 'file_path' => $path, + 'file_type' => $request->file_type, + 'active' => true, + ]); + + return redirect()->route('admin.downloads.index')->with('success', 'Download cadastrado com sucesso.'); + } + + public function edit(Download $download) + { + return view('admin.downloads.edit', compact('download')); + } + + public function update(Request $request, Download $download) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'file' => 'nullable|file|max:20480|mimes:pdf,doc,docx,zip,rar,txt,xls,xlsx', + 'file_type' => 'required|string|in:Driver,Manual,Software,Outro', + 'active' => 'required|boolean', + ], [ + 'file.mimes' => 'O arquivo deve ser do tipo: PDF, DOC, DOCX, ZIP, RAR, TXT, XLS ou XLSX.', + ]); + + $data = [ + 'title' => $request->title, + 'description' => $request->description, + 'file_type' => $request->file_type, + 'active' => $request->active, + ]; + + if ($request->hasFile('file')) { + // Remove o arquivo antigo + Storage::disk('public')->delete($download->file_path); + $data['file_path'] = $request->file('file')->store('downloads', 'public'); + } + + $download->update($data); + + return redirect()->route('admin.downloads.index')->with('success', 'Download atualizado com sucesso.'); + } + + public function destroy(Download $download) + { + Storage::disk('public')->delete($download->file_path); + $download->delete(); + return redirect()->route('admin.downloads.index')->with('success', 'Download excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/FaqController.php b/app/Http/Controllers/Admin/FaqController.php new file mode 100755 index 0000000..3309ab3 --- /dev/null +++ b/app/Http/Controllers/Admin/FaqController.php @@ -0,0 +1,60 @@ +latest()->paginate(10); + return view('admin.faqs.index', compact('faqs')); + } + + public function create() + { + return view('admin.faqs.create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'question' => 'required|string|max:255', + 'answer' => 'required|string', + 'order' => 'required|integer', + ]); + + $validated['active'] = true; + Faq::create($validated); + + return redirect()->route('admin.faqs.index')->with('success', 'FAQ criado com sucesso.'); + } + + public function edit(Faq $faq) + { + return view('admin.faqs.edit', compact('faq')); + } + + public function update(Request $request, Faq $faq) + { + $validated = $request->validate([ + 'question' => 'required|string|max:255', + 'answer' => 'required|string', + 'order' => 'required|integer', + 'active' => 'required|boolean', + ]); + + $faq->update($validated); + + return redirect()->route('admin.faqs.index')->with('success', 'FAQ atualizado com sucesso.'); + } + + public function destroy(Faq $faq) + { + $faq->delete(); + return redirect()->route('admin.faqs.index')->with('success', 'FAQ excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/GalleryCategoryController.php b/app/Http/Controllers/Admin/GalleryCategoryController.php new file mode 100755 index 0000000..e3c2e12 --- /dev/null +++ b/app/Http/Controllers/Admin/GalleryCategoryController.php @@ -0,0 +1,64 @@ +latest()->paginate(10); + return view('admin.gallery-categories.index', compact('categories')); + } + + public function create() + { + return view('admin.gallery-categories.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:gallery_categories', + 'description' => 'nullable|string', + ]); + + GalleryCategory::create([ + 'name' => $request->name, + 'slug' => $request->slug, + 'description' => $request->description, + 'active' => true, + ]); + + return redirect()->route('admin.gallery-categories.index')->with('success', 'Categoria criada com sucesso.'); + } + + public function edit(GalleryCategory $galleryCategory) + { + return view('admin.gallery-categories.edit', compact('galleryCategory')); + } + + public function update(Request $request, GalleryCategory $galleryCategory) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:gallery_categories,slug,' . $galleryCategory->id, + 'description' => 'nullable|string', + 'active' => 'required|boolean', + ]); + + $galleryCategory->update($request->all()); + + return redirect()->route('admin.gallery-categories.index')->with('success', 'Categoria atualizada com sucesso.'); + } + + public function destroy(GalleryCategory $galleryCategory) + { + $galleryCategory->delete(); + return redirect()->route('admin.gallery-categories.index')->with('success', 'Categoria excluída com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/GalleryController.php b/app/Http/Controllers/Admin/GalleryController.php new file mode 100755 index 0000000..c964188 --- /dev/null +++ b/app/Http/Controllers/Admin/GalleryController.php @@ -0,0 +1,151 @@ +latest()->paginate(10); + return view('admin.galleries.index', compact('galleries')); + } + + public function create() + { + $categories = GalleryCategory::where('active', true)->orderBy('name')->get(); + return view('admin.galleries.create', compact('categories')); + } + + public function store(Request $request) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:galleries', + 'description' => 'nullable|string', + 'category_id' => 'nullable|exists:gallery_categories,id', + ]); + + $gallery = Gallery::create([ + 'title' => $request->title, + 'slug' => $request->slug, + 'description' => $request->description, + 'category_id' => $request->category_id, + 'active' => true, + ]); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new GalleryActionNotification($gallery, $user, 'created')); + + return redirect()->route('admin.galleries.index')->with('success', 'Galeria criada com sucesso.'); + } + + public function edit(Gallery $gallery) + { + $gallery->load('images'); + $categories = GalleryCategory::where('active', true)->orderBy('name')->get(); + return view('admin.galleries.edit', compact('gallery', 'categories')); + } + + public function update(Request $request, Gallery $gallery) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:galleries,slug,' . $gallery->id, + 'description' => 'nullable|string', + 'active' => 'required|boolean', + 'category_id' => 'nullable|exists:gallery_categories,id', + ]); + + $gallery->update($request->all()); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new GalleryActionNotification($gallery, $user, 'updated')); + + return redirect()->route('admin.galleries.index')->with('success', 'Galeria atualizada com sucesso.'); + } + + public function destroy(Gallery $gallery) + { + foreach ($gallery->images as $image) { + Storage::disk('public')->delete($image->file_path); + } + $gallery->delete(); + return redirect()->route('admin.galleries.index')->with('success', 'Galeria e suas imagens excluídas.'); + } + + // Gerenciamento de Imagens + public function uploadImage(Request $request, Gallery $gallery) + { + $request->validate([ + 'images' => 'required|array|min:1|max:20', + 'images.*' => 'image|max:5120|mimes:jpeg,png,jpg,gif,webp', // 5MB each + ], [ + 'images.max' => 'Máximo de 20 imagens permitidas.', + 'images.*.mimes' => 'As imagens devem ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + ]); + + $order = $gallery->images()->count() + 1; + $count = 0; + + foreach ($request->file('images') as $file) { + $path = $file->store('galleries/' . $gallery->id, 'public'); + + GalleryImage::create([ + 'gallery_id' => $gallery->id, + 'file_path' => $path, + 'order' => $order++, + ]); + $count++; + } + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new GalleryActionNotification($gallery, $user, 'photo_added')); + + $message = $count === 1 ? 'Imagem adicionada à galeria.' : "{$count} imagens adicionadas à galeria."; + return redirect()->back()->with('success', $message); + } + + public function updateImage(Request $request, GalleryImage $image) + { + $request->validate([ + 'description' => 'nullable|string|max:255', + 'order' => 'required|integer', + ]); + + $image->update($request->only('description', 'order')); + + return redirect()->back()->with('success', 'Imagem atualizada.'); + } + + public function deleteImage(GalleryImage $image) + { + $gallery = $image->gallery; + Storage::disk('public')->delete($image->file_path); + $image->delete(); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new GalleryActionNotification($gallery, $user, 'photo_deleted')); + + return redirect()->back()->with('success', 'Imagem removida.'); + } +} diff --git a/app/Http/Controllers/Admin/MarketingController.php b/app/Http/Controllers/Admin/MarketingController.php new file mode 100755 index 0000000..85d79e4 --- /dev/null +++ b/app/Http/Controllers/Admin/MarketingController.php @@ -0,0 +1,138 @@ +route('admin.marketing.index')->with('error', 'O módulo de Produtos está desativado.'); + } + + $products = \App\Modules\Products\Models\Product::where('active', true)->with('category')->get(); + return view('admin.marketing.promote_products', compact('products')); + } + + public function promoteServices() + { + if (!module_enabled('services') || !class_exists(\App\Modules\Services\Models\Service::class)) { + return redirect()->route('admin.marketing.index')->with('error', 'O módulo de Serviços está desativado.'); + } + + $services = \App\Modules\Services\Models\Service::where('active', true)->get(); + return view('admin.marketing.promote_services', compact('services')); + } + + public function analytics() + { + // Grouping by Country + $countries = VisitorLog::select('country', DB::raw('count(*) as total')) + ->groupBy('country') + ->orderBy('total', 'desc') + ->limit(10) + ->get(); + + // Grouping by City + $cities = VisitorLog::select('city', 'region', DB::raw('count(*) as total')) + ->groupBy('city', 'region') + ->orderBy('total', 'desc') + ->limit(10) + ->get(); + + // Data for last 7 days access chart + $last7Days = collect(range(6, 0))->map(function ($days) { + $date = now()->subDays($days)->format('Y-m-d'); + return [ + 'date' => now()->subDays($days)->format('d/m'), + 'total' => VisitorLog::whereDate('created_at', $date)->count() + ]; + }); + + return view('admin.marketing.analytics', compact('countries', 'cities', 'last7Days')); + } + + public function emailMarketing() + { + $products = collect(); + $services = collect(); + + if (module_enabled('products') && class_exists(\App\Modules\Products\Models\Product::class)) { + $products = \App\Modules\Products\Models\Product::where('active', true)->get(); + } + + if (module_enabled('services') && class_exists(\App\Modules\Services\Models\Service::class)) { + $services = \App\Modules\Services\Models\Service::where('active', true)->get(); + } + + $userCount = User::where('user_type', 'User')->count(); + + return view('admin.marketing.email', compact('products', 'services', 'userCount')); + } + + public function sendEmailMarketing(Request $request) + { + $request->validate([ + 'subject' => 'required|string|max:255', + 'message' => 'required|string', + 'button_text' => 'nullable|string|max:50', + 'button_url' => 'nullable|string|max:500|regex:/^https?:\/\/.+\..+$/i', + ], [ + 'button_url.regex' => 'O link do botão deve começar com http:// ou https:// e ter um formato válido.', + ]); + + $users = User::where('user_type', 'User')->get(); + $company = (object)[ + 'name' => Setting::get('company_name', config('app.name')), + ]; + + $productImage = null; + if ($request->product_id && module_enabled('products') && class_exists(\App\Modules\Products\Models\Product::class)) { + $product = \App\Modules\Products\Models\Product::find($request->product_id); + if ($product && $product->image) { + $productImage = asset('storage/' . $product->image); + } + } elseif ($request->service_id && module_enabled('services') && class_exists(\App\Modules\Services\Models\Service::class)) { + $service = \App\Modules\Services\Models\Service::find($request->service_id); + if ($service && $service->image) { + $productImage = asset('storage/' . $service->image); + } + } + + foreach ($users as $user) { + // Check if user wants to receive emails (preference) + $prefs = $user->preferences ?? []; + if ($prefs['notifications_email'] ?? true) { + try { + Mail::to($user->email)->send(new PromotionMail( + $request->subject, + $request->message, + $request->button_text, + $request->button_url, + $productImage, + $company + )); + } catch (\Exception $e) { + Log::error("Erro ao enviar e-mail de marketing para {$user->email}: " . $e->getMessage()); + } + } + } + + return redirect()->route('admin.marketing.email')->with('success', 'E-mail de marketing enviado para ' . $users->count() . ' clientes!'); + } +} diff --git a/app/Http/Controllers/Admin/NewsController.php b/app/Http/Controllers/Admin/NewsController.php new file mode 100755 index 0000000..b908872 --- /dev/null +++ b/app/Http/Controllers/Admin/NewsController.php @@ -0,0 +1,203 @@ +latest()->paginate(10); + $newsEnabled = Setting::get('news_enabled', 'on') === 'on'; + return view('admin.news.index', compact('newsList', 'newsEnabled')); + } + + public function toggleVisibility() + { + $current = Setting::get('news_enabled', 'on'); + $newValue = $current === 'on' ? 'off' : 'on'; + Setting::set('news_enabled', $newValue, 'news'); + + $status = $newValue === 'on' ? 'ativado' : 'desativado'; + return redirect()->route('admin.news.index')->with('success', "Módulo de Notícias {$status} com sucesso."); + } + + public function create() + { + return view('admin.news.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:news', + 'summary' => 'nullable|string|max:500', + 'content' => 'nullable|string', + 'author' => 'nullable|string|max:255', + 'featured_image' => 'nullable|image|max:5120|mimes:jpeg,png,jpg,gif,webp', + 'published_at' => 'nullable|date', + 'seo_title' => 'nullable|string|max:255', + 'seo_description' => 'nullable|string|max:500', + 'seo_keywords' => 'nullable|string|max:255', + ], [ + 'featured_image.mimes' => 'A imagem deve ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + ]); + + $data = $request->except('featured_image'); + $data['active'] = true; + $data['user_id'] = Auth::id(); + + if ($request->hasFile('featured_image')) { + $data['featured_image'] = $request->file('featured_image')->store('news/featured', 'public'); + } + + $news = News::create($data); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new NewsActionNotification($news, $user, 'created')); + + return redirect()->route('admin.news.index')->with('success', 'Notícia criada com sucesso.'); + } + + public function edit(News $news) + { + $news->load('images'); + return view('admin.news.edit', compact('news')); + } + + public function update(Request $request, News $news) + { + $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:news,slug,' . $news->id, + 'summary' => 'nullable|string|max:500', + 'content' => 'nullable|string', + 'author' => 'nullable|string|max:255', + 'featured_image' => 'nullable|image|max:5120', + 'active' => 'required|boolean', + 'published_at' => 'nullable|date', + 'seo_title' => 'nullable|string|max:255', + 'seo_description' => 'nullable|string|max:500', + 'seo_keywords' => 'nullable|string|max:255', + ]); + + $data = $request->except('featured_image'); + + if ($request->hasFile('featured_image')) { + // Delete old featured image + if ($news->featured_image) { + Storage::disk('public')->delete($news->featured_image); + } + $data['featured_image'] = $request->file('featured_image')->store('news/featured', 'public'); + } + + $news->update($data); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new NewsActionNotification($news, $user, 'updated')); + + return redirect()->route('admin.news.index')->with('success', 'Notícia atualizada com sucesso.'); + } + + public function destroy(News $news) + { + // Delete featured image + if ($news->featured_image) { + Storage::disk('public')->delete($news->featured_image); + } + + // Delete all additional images + foreach ($news->images as $image) { + Storage::disk('public')->delete($image->file_path); + } + + $news->delete(); + return redirect()->route('admin.news.index')->with('success', 'Notícia excluída com sucesso.'); + } + + // Featured Image Management + public function removeFeaturedImage(News $news) + { + if ($news->featured_image) { + Storage::disk('public')->delete($news->featured_image); + $news->update(['featured_image' => null]); + } + return redirect()->back()->with('success', 'Imagem principal removida.'); + } + + // Additional Images Management + public function uploadImage(Request $request, News $news) + { + $request->validate([ + 'images' => 'required|array|min:1|max:10', + 'images.*' => 'image|max:5120|mimes:jpeg,png,jpg,gif,webp', // 5MB each + ], [ + 'images.max' => 'Máximo de 10 imagens permitidas.', + 'images.*.mimes' => 'As imagens devem ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + ]); + + $order = $news->images()->count() + 1; + $count = 0; + + foreach ($request->file('images') as $file) { + $path = $file->store('news/' . $news->id, 'public'); + + NewsImage::create([ + 'news_id' => $news->id, + 'file_path' => $path, + 'order' => $order++, + ]); + $count++; + } + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new NewsActionNotification($news, $user, 'photo_added')); + + $message = $count === 1 ? 'Imagem adicionada à notícia.' : "{$count} imagens adicionadas à notícia."; + return redirect()->back()->with('success', $message); + } + + public function updateImage(Request $request, NewsImage $image) + { + $request->validate([ + 'description' => 'nullable|string|max:255', + 'order' => 'required|integer', + ]); + + $image->update($request->only('description', 'order')); + + return redirect()->back()->with('success', 'Imagem atualizada.'); + } + + public function deleteImage(NewsImage $image) + { + $news = $image->news; + Storage::disk('public')->delete($image->file_path); + $image->delete(); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new NewsActionNotification($news, $user, 'photo_deleted')); + + return redirect()->back()->with('success', 'Imagem removida.'); + } +} diff --git a/app/Http/Controllers/Admin/PageController.php b/app/Http/Controllers/Admin/PageController.php new file mode 100755 index 0000000..ee7061d --- /dev/null +++ b/app/Http/Controllers/Admin/PageController.php @@ -0,0 +1,91 @@ +paginate(10); + return view('admin.pages.index', compact('pages')); + } + + public function create() + { + return view('admin.pages.create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:pages', + 'content' => 'nullable|string', + 'meta_description' => 'nullable|string|max:255', + 'meta_keywords' => 'nullable|string|max:255', + 'og_title' => 'nullable|string|max:255', + 'og_description' => 'nullable|string|max:255', + 'og_image' => 'nullable|string|max:255', + 'canonical_url' => 'nullable|string|max:255', + 'active' => 'required|boolean', + ]); + + $page = Page::create($validated); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + + // Notificar outros administradores sobre a nova página + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new PageActionNotification($page, $user, 'created')); + + return redirect()->route('admin.pages.index')->with('success', 'Página criada com sucesso.'); + } + + public function edit(Page $page) + { + return view('admin.pages.edit', compact('page')); + } + + public function update(Request $request, Page $page) + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:pages,slug,' . $page->id, + 'content' => 'nullable|string', + 'meta_description' => 'nullable|string|max:255', + 'meta_keywords' => 'nullable|string|max:255', + 'og_title' => 'nullable|string|max:255', + 'og_description' => 'nullable|string|max:255', + 'og_image' => 'nullable|string|max:255', + 'canonical_url' => 'nullable|string|max:255', + 'active' => 'required|boolean', + ]); + + $page->update($validated); + + /** @var \App\Models\User $user */ + $user = Auth::user(); + + // Notificar outros administradores sobre a atualização + $admins = User::where('user_type', 'Admin')->where('id', '!=', $user->id)->get(); + Notification::send($admins, new PageActionNotification($page, $user, 'updated')); + + return redirect()->route('admin.pages.index')->with('success', 'Página atualizada com sucesso.'); + } + + public function destroy(Page $page) + { + $page->delete(); + return redirect()->route('admin.pages.index')->with('success', 'Página excluída com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/SettingController.php b/app/Http/Controllers/Admin/SettingController.php new file mode 100755 index 0000000..8481712 --- /dev/null +++ b/app/Http/Controllers/Admin/SettingController.php @@ -0,0 +1,44 @@ +groupBy('group'); + return view('admin.settings.index', compact('settings')); + } + + public function update(Request $request) + { + $settings = $request->except('_token'); + + foreach ($settings as $key => $value) { + $group = 'general'; + if (str_starts_with($key, 'seo_')) $group = 'seo'; + if (str_starts_with($key, 'scripts_')) $group = 'scripts'; + if (str_starts_with($key, 'company_')) $group = 'company'; + if (str_starts_with($key, 'dashboard_')) $group = 'dashboard'; + if (str_starts_with($key, 'marketing_')) $group = 'marketing'; + + Setting::set($key, $value, $group); + } + + // Limpa o cache para garantir que as novas configurações sejam aplicadas + Artisan::call('cache:clear'); + Artisan::call('view:clear'); + + return redirect()->back()->with('success', 'Configurações globais atualizadas com sucesso.'); + } + + public function erpAd() + { + return view('admin.erp.ad'); + } +} diff --git a/app/Http/Controllers/Admin/SubjectController.php b/app/Http/Controllers/Admin/SubjectController.php new file mode 100755 index 0000000..a7bcc41 --- /dev/null +++ b/app/Http/Controllers/Admin/SubjectController.php @@ -0,0 +1,59 @@ +paginate(10); + return view('admin.subjects.index', compact('subjects')); + } + + public function create() + { + return view('admin.subjects.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:contact_subjects', + ]); + + ContactSubject::create($request->all()); + + return redirect()->route('admin.subjects.index')->with('success', 'Assunto criado com sucesso.'); + } + + public function edit(ContactSubject $subject) + { + return view('admin.subjects.edit', compact('subject')); + } + + public function update(Request $request, ContactSubject $subject) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:contact_subjects,name,' . $subject->id, + 'active' => 'required|boolean', + ]); + + $subject->update($request->all()); + + return redirect()->route('admin.subjects.index')->with('success', 'Assunto atualizado com sucesso.'); + } + + public function destroy(ContactSubject $subject) + { + if ($subject->messages()->count() > 0) { + return redirect()->route('admin.subjects.index')->with('error', 'Não é possível excluir um assunto que possui mensagens vinculadas.'); + } + + $subject->delete(); + return redirect()->route('admin.subjects.index')->with('success', 'Assunto excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100755 index 0000000..6fd5921 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,113 @@ +paginate(10); + $availablePermissions = User::getAvailablePermissions(); + $userTypes = UserType::getAllForSelect(); + return view('admin.users.index', compact('users', 'availablePermissions', 'userTypes')); + } + + public function create() + { + $availablePermissions = User::getAvailablePermissions(); + $userTypes = UserType::getAllForSelect(); + return view('admin.users.create', compact('availablePermissions', 'userTypes')); + } + + public function store(Request $request) + { + $validTypes = UserType::pluck('name')->toArray(); + + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users', + 'password' => 'required|string|min:8', + 'user_type' => 'required|string|in:' . implode(',', $validTypes), + 'company' => 'nullable|string|max:255', + 'website' => 'nullable|string|max:255|regex:/^https?:\/\/.+\..+$/i', + 'whatsapp' => 'nullable|string|max:20', + 'permissions' => 'nullable|array', + 'permissions.*' => 'string|in:' . implode(',', array_keys(User::getAvailablePermissions())), + ], [ + 'website.regex' => 'O site deve começar com http:// ou https:// e ter um formato válido.', + ]); + + $data = $request->all(); + $data['password'] = \Illuminate\Support\Facades\Hash::make($request->password); + $data['email_verified_at'] = now(); + + // Handle permissions: admin types get null (all access), others get selected permissions + $selectedType = UserType::where('name', $data['user_type'])->first(); + if ($selectedType && $selectedType->is_admin) { + $data['permissions'] = null; + } else { + $data['permissions'] = $request->has('permissions') ? $request->input('permissions') : []; + } + + User::create($data); + + return redirect()->route('admin.users.index')->with('success', 'Usuário criado com sucesso.'); + } + + public function edit(User $user) + { + $availablePermissions = User::getAvailablePermissions(); + $userTypes = UserType::getAllForSelect(); + return view('admin.users.edit', compact('user', 'availablePermissions', 'userTypes')); + } + + public function update(Request $request, User $user) + { + $validTypes = UserType::pluck('name')->toArray(); + + $request->validate([ + 'name' => 'required|string|max:255', + 'user_type' => 'required|string|in:' . implode(',', $validTypes), + 'company' => 'nullable|string|max:255', + 'website' => 'nullable|string|max:255|regex:/^https?:\/\/.+\..+$/i', + 'whatsapp' => 'nullable|string|max:20', + 'password' => 'nullable|string|min:8|confirmed', + 'permissions' => 'nullable|array', + 'permissions.*' => 'string|in:' . implode(',', array_keys(User::getAvailablePermissions())), + ], [ + 'website.regex' => 'O site deve começar com http:// ou https:// e ter um formato válido.', + ]); + + $data = $request->except('password'); + if ($request->filled('password')) { + $data['password'] = \Illuminate\Support\Facades\Hash::make($request->password); + } + + // Handle permissions: admin types get null (all access), others get selected permissions + $selectedType = UserType::where('name', $data['user_type'])->first(); + if ($selectedType && $selectedType->is_admin) { + $data['permissions'] = null; + } else { + $data['permissions'] = $request->has('permissions') ? $request->input('permissions') : []; + } + + $user->update($data); + + return redirect()->route('admin.users.index')->with('success', 'Usuário atualizado com sucesso.'); + } + + public function destroy(User $user) + { + if ($user->id === \Illuminate\Support\Facades\Auth::id()) { + return redirect()->route('admin.users.index')->with('error', 'Você não pode excluir sua própria conta.'); + } + + $user->delete(); + return redirect()->route('admin.users.index')->with('success', 'Usuário excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Admin/UserTypeController.php b/app/Http/Controllers/Admin/UserTypeController.php new file mode 100755 index 0000000..06aa881 --- /dev/null +++ b/app/Http/Controllers/Admin/UserTypeController.php @@ -0,0 +1,106 @@ +get(); + $userTypes->each(function ($type) { + $type->users_count = User::where('user_type', $type->name)->count(); + }); + return view('admin.user-types.index', compact('userTypes')); + } + + /** + * Show the form for creating a new user type. + */ + public function create() + { + return view('admin.user-types.create'); + } + + /** + * Store a newly created user type. + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:user_types,name', + 'description' => 'nullable|string|max:500', + 'is_admin' => 'nullable|boolean', + ]); + + UserType::create([ + 'name' => $request->name, + 'description' => $request->description, + 'is_admin' => $request->has('is_admin'), + 'is_protected' => false, + ]); + + return redirect()->route('admin.user-types.index')->with('success', 'Tipo de usuário criado com sucesso.'); + } + + /** + * Show the form for editing the specified user type. + */ + public function edit(UserType $userType) + { + return view('admin.user-types.edit', compact('userType')); + } + + /** + * Update the specified user type. + */ + public function update(Request $request, UserType $userType) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:user_types,name,' . $userType->id, + 'description' => 'nullable|string|max:500', + 'is_admin' => 'nullable|boolean', + ]); + + $oldName = $userType->name; + + $userType->update([ + 'name' => $request->name, + 'description' => $request->description, + 'is_admin' => $request->has('is_admin'), + ]); + + // Update all users with the old type name to the new type name + if ($oldName !== $request->name) { + User::where('user_type', $oldName)->update(['user_type' => $request->name]); + } + + return redirect()->route('admin.user-types.index')->with('success', 'Tipo de usuário atualizado com sucesso.'); + } + + /** + * Remove the specified user type. + */ + public function destroy(UserType $userType) + { + if ($userType->is_protected) { + return redirect()->route('admin.user-types.index')->with('error', 'Este tipo de usuário é protegido e não pode ser excluído.'); + } + + $userCount = User::where('user_type', $userType->name)->count(); + if ($userCount > 0) { + return redirect()->route('admin.user-types.index')->with('error', "Existem {$userCount} usuário(s) com este tipo. Altere o tipo desses usuários antes de excluir."); + } + + $userType->delete(); + + return redirect()->route('admin.user-types.index')->with('success', 'Tipo de usuário excluído com sucesso.'); + } +} diff --git a/app/Http/Controllers/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Auth/ConfirmPasswordController.php new file mode 100755 index 0000000..3559954 --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmPasswordController.php @@ -0,0 +1,39 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100755 index 0000000..08d46ff --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,43 @@ +validate([ + 'email' => 'required|email', + 'captcha' => 'required|numeric', + ]); + + if ($request->captcha != session('captcha_result')) { + throw \Illuminate\Validation\ValidationException::withMessages([ + 'captcha' => ['O resultado da soma está incorreto.'], + ]); + } + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php new file mode 100755 index 0000000..a3f0783 --- /dev/null +++ b/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,64 @@ +middleware('guest')->except('logout'); + $this->middleware('auth')->only('logout'); + } + + /** + * Validate the user login request. + * + * @param \Illuminate\Http\Request $request + * @return void + * + * @throws \Illuminate\Validation\ValidationException + */ + protected function validateLogin(Request $request) + { + $request->validate([ + $this->username() => 'required|string', + 'password' => 'required|string', + 'captcha' => 'required|numeric', + ]); + + if ($request->captcha != session('captcha_result')) { + throw \Illuminate\Validation\ValidationException::withMessages([ + 'captcha' => ['O resultado da soma está incorreto.'], + ]); + } + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php new file mode 100755 index 0000000..ee27a3f --- /dev/null +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,146 @@ +middleware('guest'); + } + + /** + * Check if public registration is enabled. + * + * @return bool + */ + protected function isRegistrationEnabled() + { + return Setting::get('public_registration', 'off') === 'on'; + } + + /** + * Show the application registration form. + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function showRegistrationForm() + { + if (!$this->isRegistrationEnabled()) { + return redirect()->route('login')->with('error', 'O registro público está desativado no momento.'); + } + + return view('auth.register'); + } + + /** + * Handle a registration request for the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + */ + public function register(\Illuminate\Http\Request $request) + { + if (!$this->isRegistrationEnabled()) { + return redirect()->route('login')->with('error', 'O registro público está desativado no momento.'); + } + + $request->validate(['captcha' => 'required|numeric']); + if ($request->captcha != session('captcha_result')) { + throw \Illuminate\Validation\ValidationException::withMessages([ + 'captcha' => ['O resultado da soma está incorreto.'], + ]); + } + + $this->validator($request->all())->validate(); + + event(new \Illuminate\Auth\Events\Registered($user = $this->create($request->all()))); + + $this->guard()->login($user); + + if ($response = $this->registered($request, $user)) { + return $response; + } + + return $request->wantsJson() + ? new \Illuminate\Http\JsonResponse([], 201) + : redirect($this->redirectPath()); + } + + /** + * Get a validator for an incoming registration request. + * + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + 'company' => ['nullable', 'string', 'max:255'], + 'website' => ['nullable', 'string', 'max:255'], + 'whatsapp' => ['nullable', 'string', 'max:20'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @return User + */ + protected function create(array $data) + { + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + 'user_type' => 'User', // Force all public registrations to be regular users + 'company' => $data['company'] ?? null, + 'website' => $data['website'] ?? null, + 'whatsapp' => $data['whatsapp'] ?? null, + 'permissions' => [], + ]); + + // Notificar administradores sobre o novo cadastro + $admins = User::where('user_type', 'Admin')->get(); + Notification::send($admins, new NewUserRegisteredNotification($user)); + + return $user; + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100755 index 0000000..f6be189 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,51 @@ +validate([ + 'captcha' => 'required|numeric', + ]); + + if ($request->captcha != session('captcha_result')) { + throw \Illuminate\Validation\ValidationException::withMessages([ + 'captcha' => ['O resultado da soma está incorreto.'], + ]); + } + + return parent::reset($request); + } +} diff --git a/app/Http/Controllers/Auth/VerificationController.php b/app/Http/Controllers/Auth/VerificationController.php new file mode 100755 index 0000000..23a43a8 --- /dev/null +++ b/app/Http/Controllers/Auth/VerificationController.php @@ -0,0 +1,41 @@ +middleware('auth'); + $this->middleware('signed')->only('verify'); + $this->middleware('throttle:6,1')->only('verify', 'resend'); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100755 index 0000000..77ec359 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,12 @@ +middleware('auth'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + $metrics = [ + 'pending_tickets' => ContactMessage::where('read', false)->count(), + 'total_contacts' => ContactMessage::count(), + 'total_users' => User::where('user_type', 'User')->count(), + ]; + + // Products module metrics (only when module is enabled) + if (module_enabled('products') && class_exists(\App\Modules\Products\Models\Product::class)) { + $metrics['total_products'] = \App\Modules\Products\Models\Product::where('active', true)->count(); + } + + // Services module metrics (only when module is enabled) + if (module_enabled('services') && class_exists(\App\Modules\Services\Models\Service::class)) { + $metrics['total_services'] = \App\Modules\Services\Models\Service::where('active', true)->count(); + } + + return view('home', compact('metrics')); + } + + /** + * Send a feature request to SysSiteCMS. + */ + public function sendFeatureRequest(Request $request) + { + $request->validate([ + 'feature_description' => 'required|string|min:10', + ]); + + $user = Auth::user(); + $description = $request->feature_description; + + // Send notification to SysSiteCMS email + Notification::route('mail', 'contato@syspanel.com.br') + ->notify(new FeatureRequestNotification($user, $description)); + + return redirect()->back()->with('success', 'Sua solicitação foi enviada com sucesso ao SysSiteCMS! Em breve entraremos em contato.'); + } +} diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php new file mode 100755 index 0000000..4ada970 --- /dev/null +++ b/app/Http/Controllers/PageController.php @@ -0,0 +1,174 @@ +where('active', true)->firstOrFail(); + $banners = Banner::where('active', true)->orderBy('order')->get(); + return view('index', compact('page', 'banners')); + } + + public function about() + { + $page = Page::where('slug', 'about')->where('active', true)->firstOrFail(); + return view('about', compact('page')); + } + + // Note: produtos() is now handled by the Products module (ProductPageController) + + // Note: servicos() is now handled by the Services module (ServicePageController) + + public function assistentesDigitaisIA() + { + $page = Page::where('slug', 'assistentes-digitais-com-ia')->where('active', true)->firstOrFail(); + return view('assistentes-digitais-ia', compact('page')); + } + + public function servidoresLinux() + { + $page = Page::where('slug', 'linux-servers')->where('active', true)->firstOrFail(); + return view('servidores-linux', compact('page')); + } + + public function erp() + { + $page = Page::where('slug', 'erp')->where('active', true)->firstOrFail(); + return view('erp', compact('page')); + } + + public function fluxoDeCaixaPro() + { + $page = Page::where('slug', 'fluxo-de-caixa-pro')->where('active', true)->firstOrFail(); + return view('fluxo-de-caixa-pro', compact('page')); + } + + public function downloads() + { + $page = Page::where('slug', 'downloads')->where('active', true)->firstOrFail(); + $downloads = Download::where('active', true)->latest()->get(); + return view('downloads', compact('downloads', 'page')); + } + + public function downloadFile(Download $download) + { + // Only allow download if the download is active + if (!$download->active) { + abort(404, 'Download não encontrado.'); + } + + $download->increment('download_count'); + return response()->download(storage_path('app/public/' . $download->file_path)); + } + + public function faq() + { + $page = Page::where('slug', 'faq')->where('active', true)->firstOrFail(); + $faqs = Faq::where('active', true)->orderBy('order')->get(); + return view('faq', compact('faqs', 'page')); + } + + public function galleryCategory($slug) + { + $category = GalleryCategory::where('slug', $slug)->where('active', true)->firstOrFail(); + $galleries = Gallery::where('active', true)->where('category_id', $category->id)->with('images')->withCount('images')->orderBy('title')->paginate(12); + return view('gallery-category', compact('category', 'galleries')); + } + + public function gallery($slug) + { + $gallery = Gallery::where('slug', $slug)->where('active', true)->with('images')->firstOrFail(); + return view('gallery-show', compact('gallery')); + } + + public function newsIndex() + { + $newsList = News::published()->latest('published_at')->latest()->paginate(9); + return view('news-index', compact('newsList')); + } + + public function newsShow($slug) + { + $news = News::where('slug', $slug)->published()->with('images')->firstOrFail(); + return view('news-show', compact('news')); + } + + /** + * Show a custom dynamic page. + */ + public function show($slug) + { + $page = Page::where('slug', $slug)->where('active', true)->firstOrFail(); + return view('page', compact('page')); + } + + public function contato() + { + $page = Page::where('slug', 'contato')->where('active', true)->firstOrFail(); + $subjects = ContactSubject::where('active', true)->orderBy('name')->get(); + return view('contato', compact('subjects', 'page')); + } + + public function sendContact(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:255', + 'subject_id' => 'required|exists:contact_subjects,id', + 'message' => 'required|string|min:10', + 'whatsapp' => 'nullable|string|max:20|regex:/^[\d\s\(\)\+\-]{10,20}$/', + 'company' => 'nullable|string|max:255', + 'website' => 'nullable|string|max:255|regex:/^https?:\/\/.+\..+$/i', + 'captcha' => 'required|numeric', + ], [ + 'whatsapp.regex' => 'O formato do WhatsApp é inválido.', + 'website.regex' => 'O site deve começar com http:// ou https:// e ter um formato válido.', + ]); + + // Captcha validation with timing-safe comparison + if (!hash_equals((string) session('captcha_result'), (string) $request->captcha)) { + return back()->withErrors(['captcha' => 'O resultado da soma está incorreto.'])->withInput(); + } + + // Create contact message with explicit fields + $contact = ContactMessage::create([ + 'name' => $request->name, + 'email' => $request->email, + 'company' => $request->company, + 'website' => $request->website, + 'whatsapp' => $request->whatsapp, + 'subject_id' => $request->subject_id, + 'message' => $request->message, + ]); + + // Notify administrators + $admins = User::where('user_type', 'Admin')->get(); + Notification::send($admins, new NewContactMessageNotification($contact)); + + return Redirect::route('contato')->with('success', 'Sua mensagem foi enviada com sucesso! Em breve entraremos em contato.'); + } + + public function politicaPrivacidade() + { + $page = Page::where('slug', 'politica-privacidade')->where('active', true)->first(); + return view('politica-privacidade', compact('page')); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100755 index 0000000..8b9c9cb --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,116 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], + 'avatar' => ['nullable', 'image', 'max:2048', 'mimes:jpeg,png,jpg,gif,webp'], // Max 2MB + 'company' => ['nullable', 'string', 'max:255'], + 'website' => ['nullable', 'url', 'max:255'], + 'whatsapp' => ['nullable', 'string', 'max:20', 'regex:/^[\d\s\(\)\+\-]{10,20}$/'], + 'password' => ['nullable', 'string', 'min:8', 'confirmed'], + ], [ + 'avatar.mimes' => 'O avatar deve ser do tipo: JPEG, PNG, JPG, GIF ou WebP.', + 'website.url' => 'O formato do site é inválido.', + 'whatsapp.regex' => 'O formato do WhatsApp é inválido.', + ]); + + $data = $request->except(['password', 'avatar']); + + if ($request->filled('password')) { + $data['password'] = Hash::make($request->password); + } + + if ($request->hasFile('avatar')) { + // Remove avatar antigo se existir + if ($user->avatar) { + Storage::disk('public')->delete($user->avatar); + } + $data['avatar'] = $request->file('avatar')->store('avatars', 'public'); + } + + $user->update($data); + + return redirect()->route('profile.edit')->with('success', 'Perfil atualizado com sucesso!'); + } + + /** + * Show user preferences. + */ + public function preferences() + { + $user = Auth::user(); + $preferences = $user->preferences ?? [ + 'notifications_email' => true, + 'notifications_system' => true, + 'notifications_new_users' => true, + 'notifications_page_actions' => true, + 'notifications_gallery_actions' => true, + 'notifications_product_actions' => true, + 'theme' => 'light' + ]; + return view('profile.preferences', compact('user', 'preferences')); + } + + /** + * Update user preferences. + */ + public function updatePreferences(Request $request) + { + /** @var \App\Models\User $user */ + $user = Auth::user(); + + $user->update([ + 'preferences' => [ + 'notifications_email' => $request->has('notifications_email'), + 'notifications_system' => $request->has('notifications_system'), + 'notifications_new_users' => $request->has('notifications_new_users'), + 'notifications_page_actions' => $request->has('notifications_page_actions'), + 'notifications_gallery_actions' => $request->has('notifications_gallery_actions'), + 'notifications_product_actions' => $request->has('notifications_product_actions'), + 'theme' => $request->theme ?? 'light' + ] + ]); + + if ($user->user_type === 'Admin') { + Setting::set('site_maintenance', $request->has('site_maintenance') ? 'on' : 'off', 'general'); + Setting::set('whatsapp_floating_button', $request->has('whatsapp_floating_button') ? 'on' : 'off', 'general'); + Setting::set('public_registration', $request->has('public_registration') ? 'on' : 'off', 'general'); + + // Salvar configurações de horário do WhatsApp + Setting::set('whatsapp_start_time', $request->whatsapp_start_time ?? '08:00', 'general'); + Setting::set('whatsapp_end_time', $request->whatsapp_end_time ?? '18:00', 'general'); + Setting::set('whatsapp_lunch_start', $request->whatsapp_lunch_start, 'general'); + Setting::set('whatsapp_lunch_end', $request->whatsapp_lunch_end, 'general'); + } + + return redirect()->route('profile.preferences')->with('success', 'Preferências atualizadas com sucesso!'); + } +} diff --git a/app/Http/Middleware/CheckMaintenanceMode.php b/app/Http/Middleware/CheckMaintenanceMode.php new file mode 100755 index 0000000..fa31839 --- /dev/null +++ b/app/Http/Middleware/CheckMaintenanceMode.php @@ -0,0 +1,44 @@ +is('login') || $request->is('logout') || $request->is('admin*') || $request->is('password*')) { + return $next($request); + } + + /** @var \App\Models\User $user */ + $user = Auth::user(); + + // Permite que administradores continuem acessando o site público + if ($user && $user->user_type === 'Admin') { + return $next($request); + } + + // Para todos os outros usuários ou visitantes, exibe a página de manutenção + return response()->view('errors.maintenance', [], 503); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/CheckPermission.php b/app/Http/Middleware/CheckPermission.php new file mode 100755 index 0000000..90a1b13 --- /dev/null +++ b/app/Http/Middleware/CheckPermission.php @@ -0,0 +1,37 @@ +route('login'); + } + + $user = Auth::user(); + + if ($user->isAdmin()) { + return $next($request); + } + + if ($user->hasPermission($permission)) { + return $next($request); + } + + abort(403, 'Acesso negado. Você não tem permissão para acessar esta seção.'); + } +} diff --git a/app/Http/Middleware/EnsureUserIsAdmin.php b/app/Http/Middleware/EnsureUserIsAdmin.php new file mode 100755 index 0000000..a868b6b --- /dev/null +++ b/app/Http/Middleware/EnsureUserIsAdmin.php @@ -0,0 +1,25 @@ +user_type === 'Admin') { + return $next($request); + } + + abort(403, 'Acesso negado. Esta área é restrita a administradores.'); + } +} diff --git a/app/Http/Middleware/TrackVisitor.php b/app/Http/Middleware/TrackVisitor.php new file mode 100755 index 0000000..1b50bd5 --- /dev/null +++ b/app/Http/Middleware/TrackVisitor.php @@ -0,0 +1,56 @@ +isMethod('GET') && !$request->is('admin*') && !$request->is('login') && !$request->is('logout')) { + + $ip = $request->ip(); + $sessionId = session()->getId(); + + // Tenta obter localização do cache para não sobrecarregar a API nem atrasar o site + $location = Cache::remember('visitor_location_' . $ip, now()->addDays(7), function () use ($ip) { + try { + // Usando ip-api.com (serviço gratuito limitado) + $response = Http::timeout(3)->get("http://ip-api.com/json/{$ip}?fields=status,country,city,regionName"); + + if ($response->successful() && $response->json('status') === 'success') { + return $response->json(); + } + } catch (\Exception $e) { + Log::warning("Erro ao rastrear localização do IP {$ip}: " . $e->getMessage()); + } + + return [ + 'country' => 'Desconhecido', + 'city' => 'Desconhecido', + 'regionName' => 'Desconhecido' + ]; + }); + + // Registra o log + VisitorLog::create([ + 'ip_address' => $ip, + 'country' => $location['country'] ?? 'Desconhecido', + 'city' => $location['city'] ?? 'Desconhecido', + 'region' => $location['regionName'] ?? 'Desconhecido', + 'session_id' => $sessionId, + 'user_agent' => $request->userAgent(), + 'page_url' => $request->fullUrl() + ]); + } + + return $next($request); + } +} diff --git a/app/Mail/ContactReplyMail.php b/app/Mail/ContactReplyMail.php new file mode 100755 index 0000000..ee3d5dc --- /dev/null +++ b/app/Mail/ContactReplyMail.php @@ -0,0 +1,47 @@ +contact = $contact; + $this->replyMessage = $replyMessage; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: 'Re: ' . ($this->contact->subject->name ?? 'Contato SysSiteCMS'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'emails.contact-reply', + ); + } +} diff --git a/app/Mail/PromotionMail.php b/app/Mail/PromotionMail.php new file mode 100755 index 0000000..b0b0068 --- /dev/null +++ b/app/Mail/PromotionMail.php @@ -0,0 +1,64 @@ +subjectLine = $subject; + $this->messageBody = $message; + $this->buttonText = $btnText; + $this->buttonUrl = $btnUrl; + $this->productImage = $image; + $this->company = $company; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + subject: $this->subjectLine, + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'emails.promotion', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Banner.php b/app/Models/Banner.php new file mode 100755 index 0000000..1baf3e1 --- /dev/null +++ b/app/Models/Banner.php @@ -0,0 +1,10 @@ +belongsTo(ContactSubject::class, 'subject_id'); + } + + public function responses() + { + return $this->hasMany(ContactResponse::class, 'contact_message_id'); + } +} diff --git a/app/Models/ContactResponse.php b/app/Models/ContactResponse.php new file mode 100755 index 0000000..b3933a1 --- /dev/null +++ b/app/Models/ContactResponse.php @@ -0,0 +1,20 @@ +belongsTo(ContactMessage::class, 'contact_message_id'); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/ContactSubject.php b/app/Models/ContactSubject.php new file mode 100755 index 0000000..c5ba429 --- /dev/null +++ b/app/Models/ContactSubject.php @@ -0,0 +1,15 @@ +hasMany(ContactMessage::class, 'subject_id'); + } +} diff --git a/app/Models/Download.php b/app/Models/Download.php new file mode 100755 index 0000000..55b987c --- /dev/null +++ b/app/Models/Download.php @@ -0,0 +1,10 @@ +hasMany(GalleryImage::class)->orderBy('order'); + } + + public function category() + { + return $this->belongsTo(GalleryCategory::class); + } +} diff --git a/app/Models/GalleryCategory.php b/app/Models/GalleryCategory.php new file mode 100755 index 0000000..5da691f --- /dev/null +++ b/app/Models/GalleryCategory.php @@ -0,0 +1,15 @@ +hasMany(Gallery::class, 'category_id')->orderBy('created_at', 'desc'); + } +} diff --git a/app/Models/GalleryImage.php b/app/Models/GalleryImage.php new file mode 100755 index 0000000..1c3d065 --- /dev/null +++ b/app/Models/GalleryImage.php @@ -0,0 +1,15 @@ +belongsTo(Gallery::class); + } +} diff --git a/app/Models/News.php b/app/Models/News.php new file mode 100755 index 0000000..b871016 --- /dev/null +++ b/app/Models/News.php @@ -0,0 +1,66 @@ + 'boolean', + 'published_at' => 'datetime', + ]; + + /** + * Get the images for the news article. + */ + public function images(): HasMany + { + return $this->hasMany(NewsImage::class)->orderBy('order'); + } + + /** + * Get the user who created the news article. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Check if the news article is published. + */ + public function isPublished(): bool + { + return $this->active && ($this->published_at === null || $this->published_at->isPast()); + } + + /** + * Scope to get only published news. + */ + public function scopePublished($query) + { + return $query->where('active', true) + ->where(function ($q) { + $q->whereNull('published_at') + ->orWhere('published_at', '<=', now()); + }); + } +} diff --git a/app/Models/NewsImage.php b/app/Models/NewsImage.php new file mode 100755 index 0000000..8af3ea3 --- /dev/null +++ b/app/Models/NewsImage.php @@ -0,0 +1,24 @@ +belongsTo(News::class); + } +} diff --git a/app/Models/Page.php b/app/Models/Page.php new file mode 100755 index 0000000..096a094 --- /dev/null +++ b/app/Models/Page.php @@ -0,0 +1,21 @@ +first(); + return ($setting && $setting->value !== '' && $setting->value !== null) ? $setting->value : $default; + } + + public static function set($key, $value, $group = 'general') + { + return self::updateOrCreate(['key' => $key], ['value' => $value, 'group' => $group]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100755 index 0000000..02929a1 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,87 @@ + */ + use HasFactory, Notifiable; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'preferences' => 'array', + 'permissions' => 'array', + ]; + } + + /** + * Check if the user is an Admin. + */ + public function isAdmin(): bool + { + $adminType = \App\Models\UserType::where('is_admin', true)->pluck('name')->toArray(); + return in_array($this->user_type, $adminType); + } + + /** + * Check if the user is a regular User. + */ + public function isUser(): bool + { + return !$this->isAdmin(); + } + + /** + * Check if the user has a specific permission. + * Admin types always have all permissions. + */ + public function hasPermission(string $permission): bool + { + if ($this->isAdmin()) { + return true; + } + + return in_array($permission, $this->permissions ?? []); + } + + /** + * Get all available permissions in the system. + * + * @return array + */ + public static function getAvailablePermissions(): array + { + return [ + 'contacts' => 'Gerenciar Contatos', + 'subjects' => 'Gerenciar Assuntos', + 'services' => 'Gerenciar Serviços', + 'faqs' => 'Gerenciar FAQ', + 'downloads' => 'Gerenciar Downloads', + 'products' => 'Gerenciar Produtos', + 'galleries' => 'Gerenciar Galerias', + 'news' => 'Gerenciar Notícias', + 'pages' => 'Gerenciar Páginas', + 'marketing' => 'Marketing', + 'chatbot' => 'Gerenciar Chatbot', + 'settings' => 'Dados da Empresa', + ]; + } +} diff --git a/app/Models/UserType.php b/app/Models/UserType.php new file mode 100755 index 0000000..e5c41df --- /dev/null +++ b/app/Models/UserType.php @@ -0,0 +1,36 @@ + 'boolean', + 'is_protected' => 'boolean', + ]; + } + + /** + * Get the users with this type. + */ + public function users() + { + return User::where('user_type', $this->name); + } + + /** + * Get all user types as a key-value array for select fields. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function getAllForSelect() + { + return self::orderBy('name')->get(); + } +} diff --git a/app/Models/VisitorLog.php b/app/Models/VisitorLog.php new file mode 100755 index 0000000..93d5139 --- /dev/null +++ b/app/Models/VisitorLog.php @@ -0,0 +1,18 @@ +orderBy('title') + ->paginate(20); + + $categories = ChatbotKnowledge::getCategories(); + + return view('chatbot::admin.index', compact('knowledgeEntries', 'categories')); + } + + /** + * Show the form for creating a new knowledge entry. + */ + public function create(): \Illuminate\View\View + { + $categories = ChatbotKnowledge::getCategories(); + return view('chatbot::admin.create', compact('categories')); + } + + /** + * Store a newly created knowledge entry. + */ + public function store(Request $request): \Illuminate\Http\RedirectResponse + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'restrictions' => 'nullable|string', + 'category' => 'required|string|in:' . implode(',', array_keys(ChatbotKnowledge::getCategories())), + ]); + + $validated['active'] = $request->has('active'); + + ChatbotKnowledge::create($validated); + + return redirect() + ->route('admin.chatbot.index') + ->with('success', 'Informação adicionada à base de conhecimento com sucesso!'); + } + + /** + * Show the form for editing a knowledge entry. + */ + public function edit(ChatbotKnowledge $chatbot_knowledge): \Illuminate\View\View + { + $entry = $chatbot_knowledge; + $categories = ChatbotKnowledge::getCategories(); + return view('chatbot::admin.edit', compact('entry', 'categories')); + } + + /** + * Update a knowledge entry. + */ + public function update(Request $request, ChatbotKnowledge $chatbot_knowledge): \Illuminate\Http\RedirectResponse + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'restrictions' => 'nullable|string', + 'category' => 'required|string|in:' . implode(',', array_keys(ChatbotKnowledge::getCategories())), + ]); + + $validated['active'] = $request->has('active'); + + $chatbot_knowledge->update($validated); + + return redirect() + ->route('admin.chatbot.index') + ->with('success', 'Informação atualizada com sucesso!'); + } + + /** + * Delete a knowledge entry. + */ + public function destroy(ChatbotKnowledge $chatbot_knowledge): \Illuminate\Http\RedirectResponse + { + $chatbot_knowledge->delete(); + + return redirect() + ->route('admin.chatbot.index') + ->with('success', 'Informação removida da base de conhecimento.'); + } + + /** + * Display conversations listing with visitor contacts. + */ + public function conversations(): \Illuminate\View\View + { + $conversations = ChatbotConversation::with('messages') + ->whereNotNull('visitor_name') + ->orderBy('created_at', 'desc') + ->paginate(20); + + return view('chatbot::admin.conversations', compact('conversations')); + } + + /** + * Display a specific conversation with all messages. + */ + public function showConversation(ChatbotConversation $conversation): \Illuminate\View\View + { + $conversation->load('messages'); + + return view('chatbot::admin.conversation-show', compact('conversation')); + } + + /** + * Delete a conversation. + */ + public function destroyConversation(ChatbotConversation $conversation): \Illuminate\Http\RedirectResponse + { + $conversation->delete(); + + return redirect() + ->route('admin.chatbot.conversations') + ->with('success', 'Conversa removida com sucesso.'); + } + + /** + * Display chatbot settings. + */ + public function settings(): \Illuminate\View\View + { + $settings = [ + 'chatbot_enabled' => Setting::get('chatbot_enabled', 'off'), + 'chatbot_provider' => Setting::get('chatbot_provider', 'openrouter'), + 'chatbot_openrouter_api_key' => Setting::get('chatbot_openrouter_api_key'), + 'chatbot_openrouter_model' => Setting::get('chatbot_openrouter_model', 'openai/gpt-3.5-turbo'), + 'chatbot_llamacpp_base_url' => Setting::get('chatbot_llamacpp_base_url', 'http://172.17.0.1:8080'), + 'chatbot_llamacpp_model' => Setting::get('chatbot_llamacpp_model', ''), + 'chatbot_welcome_message' => Setting::get('chatbot_welcome_message', 'Olá! Como posso ajudar você hoje?'), + 'chatbot_custom_instructions' => Setting::get('chatbot_custom_instructions'), + 'chatbot_max_tokens' => Setting::get('chatbot_max_tokens', '500'), + 'chatbot_temperature' => Setting::get('chatbot_temperature', '0.7'), + ]; + + return view('chatbot::admin.settings', compact('settings')); + } + + /** + * Update chatbot settings. + */ + public function updateSettings(Request $request): \Illuminate\Http\RedirectResponse + { + $validated = $request->validate([ + 'chatbot_enabled' => 'nullable|in:on,off', + 'chatbot_provider' => 'required|in:openrouter,llamacpp', + 'chatbot_openrouter_api_key' => 'nullable|string|max:255', + 'chatbot_openrouter_model' => 'nullable|string|max:255', + 'chatbot_llamacpp_base_url' => 'nullable|string|max:255', + 'chatbot_llamacpp_model' => 'nullable|string|max:255', + 'chatbot_welcome_message' => 'required|string|max:500', + 'chatbot_custom_instructions' => 'nullable|string', + 'chatbot_max_tokens' => 'required|integer|min:50|max:2000', + 'chatbot_temperature' => 'required|numeric|min:0|max:2', + ]); + + foreach ($validated as $key => $value) { + if ($key === 'chatbot_enabled') { + Setting::set($key, $value ?? 'off', 'chatbot'); + } else { + Setting::set($key, $value, 'chatbot'); + } + } + + // Ensure enabled is set properly when checkbox is not checked + if (!$request->has('chatbot_enabled')) { + Setting::set('chatbot_enabled', 'off', 'chatbot'); + } + + return redirect() + ->route('admin.chatbot.settings') + ->with('success', 'Configurações do chatbot atualizadas com sucesso!'); + } + + /** + * Display the chatbot rules listing. + */ + public function rulesIndex(): \Illuminate\View\View + { + $rules = ChatbotRule::orderBy('order') + ->orderBy('id') + ->paginate(30); + + return view('chatbot::admin.rules.index', compact('rules')); + } + + /** + * Show the form for creating a new chatbot rule. + */ + public function rulesCreate(): \Illuminate\View\View + { + return view('chatbot::admin.rules.create'); + } + + /** + * Store a newly created chatbot rule. + */ + public function rulesStore(Request $request): \Illuminate\Http\RedirectResponse + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string|max:2000', + 'order' => 'nullable|integer|min:0|max:9999', + ]); + + $validated['active'] = $request->has('active'); + $validated['order'] = $validated['order'] ?? 0; + + ChatbotRule::create($validated); + + return redirect() + ->route('admin.chatbot.rules.index') + ->with('success', 'Regra criada com sucesso! A IA passará a segui-la nas próximas conversas.'); + } + + /** + * Show the form for editing a chatbot rule. + */ + public function rulesEdit(ChatbotRule $rule): \Illuminate\View\View + { + return view('chatbot::admin.rules.edit', compact('rule')); + } + + /** + * Update a chatbot rule. + */ + public function rulesUpdate(Request $request, ChatbotRule $rule): \Illuminate\Http\RedirectResponse + { + $validated = $request->validate([ + 'title' => 'required|string|max:255', + 'content' => 'required|string|max:2000', + 'order' => 'nullable|integer|min:0|max:9999', + ]); + + $validated['active'] = $request->has('active'); + $validated['order'] = $validated['order'] ?? 0; + + $rule->update($validated); + + return redirect() + ->route('admin.chatbot.rules.index') + ->with('success', 'Regra atualizada com sucesso!'); + } + + /** + * Delete a chatbot rule. + */ + public function rulesDestroy(ChatbotRule $rule): \Illuminate\Http\RedirectResponse + { + $rule->delete(); + + return redirect() + ->route('admin.chatbot.rules.index') + ->with('success', 'Regra removida com sucesso.'); + } +} diff --git a/app/Modules/Chatbot/Http/Controllers/ChatbotApiController.php b/app/Modules/Chatbot/Http/Controllers/ChatbotApiController.php new file mode 100755 index 0000000..b5fd025 --- /dev/null +++ b/app/Modules/Chatbot/Http/Controllers/ChatbotApiController.php @@ -0,0 +1,259 @@ +json([ + 'success' => false, + 'message' => 'O assistente virtual está indisponível no momento.', + ], 403); + } + + $validator = Validator::make($request->all(), [ + 'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/', + 'name' => 'required|string|max:255', + 'phone' => 'required|string|max:30', + 'city' => 'required|string|max:255', + ], [ + 'session_id.regex' => 'ID de sessão inválido.', + 'name.required' => 'Por favor, informe seu nome.', + 'phone.required' => 'Por favor, informe seu telefone.', + 'city.required' => 'Por favor, informe sua cidade.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Preencha todos os campos obrigatórios.', + 'errors' => $validator->errors(), + ], 422); + } + + $sessionId = $request->input('session_id'); + + // Get or create conversation and save visitor info + $conversation = ChatbotConversation::getOrCreateBySession($sessionId); + $conversation->update([ + 'visitor_name' => trim($request->input('name')), + 'visitor_phone' => trim($request->input('phone')), + 'visitor_city' => trim($request->input('city')), + ]); + + // Send notification to admins + $adminTypes = \App\Models\UserType::where('is_admin', true)->pluck('name')->toArray(); + $admins = User::whereIn('user_type', $adminTypes)->get(); + + if ($admins->isNotEmpty()) { + Notification::send($admins, new ChatbotConversationNotification($conversation)); + } + + return response()->json([ + 'success' => true, + 'message' => 'Dados registrados com sucesso.', + ]); + } + + /** + * Handle an incoming chat message from the public widget. + */ + public function sendMessage(Request $request): JsonResponse + { + // Check if chatbot is enabled + if (Setting::get('chatbot_enabled', 'off') !== 'on') { + return response()->json([ + 'success' => false, + 'message' => 'O assistente virtual está indisponível no momento.', + ], 403); + } + + $validator = Validator::make($request->all(), [ + 'message' => 'required|string|max:1000', + 'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/', + ], [ + 'session_id.regex' => 'ID de sessão inválido.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Mensagem inválida.', + 'errors' => $validator->errors(), + ], 422); + } + + $sessionId = $request->input('session_id'); + $userMessage = trim($request->input('message')); + + // Get or create conversation + $conversation = ChatbotConversation::getOrCreateBySession($sessionId); + + // Check if conversation is ended + if ($conversation->isEnded()) { + return response()->json([ + 'success' => false, + 'message' => 'Esta conversa foi finalizada. Inicie uma nova conversa.', + 'conversation_ended' => true, + ], 403); + } + + // Save user message + ChatbotMessage::create([ + 'conversation_id' => $conversation->id, + 'role' => 'user', + 'content' => $userMessage, + ]); + + // Get conversation context + $context = $conversation->getContextForApi(20); + + // Call the configured AI provider (OpenRouter or llama.cpp) + $provider = Setting::get('chatbot_provider', 'openrouter'); + + if ($provider === 'llamacpp') { + $aiService = new LlamaCppService(); + } else { + $aiService = new OpenRouterService(); + } + + $result = $aiService->chat($context, $conversation); + + // Save assistant message with token usage + ChatbotMessage::create([ + 'conversation_id' => $conversation->id, + 'role' => 'assistant', + 'content' => $result['content'], + 'tokens_used' => $result['tokens_used'], + ]); + + return response()->json([ + 'success' => true, + 'message' => $result['content'], + ]); + } + + /** + * End a conversation. + */ + public function endConversation(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/', + ], [ + 'session_id.regex' => 'ID de sessão inválido.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Sessão inválida.', + ], 422); + } + + $sessionId = $request->input('session_id'); + $conversation = ChatbotConversation::where('session_id', $sessionId)->first(); + + if (!$conversation) { + return response()->json([ + 'success' => false, + 'message' => 'Conversa não encontrada.', + ], 404); + } + + if ($conversation->isEnded()) { + return response()->json([ + 'success' => true, + 'message' => 'Conversa já foi finalizada.', + ]); + } + + $conversation->update([ + 'status' => 'ended', + 'ended_at' => now(), + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Conversa finalizada com sucesso.', + ]); + } + + /** + * Submit a rating for a conversation. + */ + public function rateConversation(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/', + 'rating' => 'required|integer|min:1|max:5', + 'comment' => 'nullable|string|max:500', + ], [ + 'session_id.regex' => 'ID de sessão inválido.', + 'rating.required' => 'Por favor, selecione uma avaliação.', + 'rating.min' => 'A avaliação mínima é 1 estrela.', + 'rating.max' => 'A avaliação máxima é 5 estrelas.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Avaliação inválida.', + 'errors' => $validator->errors(), + ], 422); + } + + $sessionId = $request->input('session_id'); + $conversation = ChatbotConversation::where('session_id', $sessionId)->first(); + + if (!$conversation) { + return response()->json([ + 'success' => false, + 'message' => 'Conversa não encontrada.', + ], 404); + } + + $conversation->update([ + 'rating' => $request->input('rating'), + 'rating_comment' => $request->input('comment'), + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Avaliação registrada. Obrigado pelo feedback!', + ]); + } + + /** + * Get the initial welcome message and chatbot status. + */ + public function getStatus(): JsonResponse + { + $enabled = Setting::get('chatbot_enabled', 'off') === 'on'; + $welcomeMessage = Setting::get('chatbot_welcome_message', 'Olá! Como posso ajudar você hoje?'); + + return response()->json([ + 'enabled' => $enabled, + 'welcome_message' => $welcomeMessage, + ]); + } +} diff --git a/app/Modules/Chatbot/Models/ChatbotConversation.php b/app/Modules/Chatbot/Models/ChatbotConversation.php new file mode 100755 index 0000000..2e67f0d --- /dev/null +++ b/app/Modules/Chatbot/Models/ChatbotConversation.php @@ -0,0 +1,92 @@ + 'integer', + 'ended_at' => 'datetime', + ]; + + /** + * Get the messages for this conversation. + */ + public function messages(): HasMany + { + return $this->hasMany(ChatbotMessage::class, 'conversation_id'); + } + + /** + * Get or create a conversation by session ID. + */ + public static function getOrCreateBySession(string $sessionId): self + { + return self::firstOrCreate( + ['session_id' => $sessionId], + ['session_id' => $sessionId] + ); + } + + /** + * Check if the conversation is active. + */ + public function isActive(): bool + { + return $this->status === 'active'; + } + + /** + * Check if the conversation is ended. + */ + public function isEnded(): bool + { + return $this->status === 'ended'; + } + + /** + * Get the conversation context as an array of messages for the API. + */ + public function getContextForApi(int $maxMessages = 20): array + { + $messages = $this->messages() + ->orderBy('created_at', 'asc') + ->take($maxMessages) + ->get(['role', 'content']); + + return $messages->map(fn($m) => [ + 'role' => $m->role, + 'content' => $m->content, + ])->toArray(); + } + + /** + * Get the rating as star display text. + */ + public function getRatingStarsAttribute(): string + { + if (!$this->rating) { + return '—'; + } + + $stars = str_repeat('★', $this->rating) . str_repeat('☆', 5 - $this->rating); + return $stars; + } +} diff --git a/app/Modules/Chatbot/Models/ChatbotKnowledge.php b/app/Modules/Chatbot/Models/ChatbotKnowledge.php new file mode 100755 index 0000000..4ecfd45 --- /dev/null +++ b/app/Modules/Chatbot/Models/ChatbotKnowledge.php @@ -0,0 +1,73 @@ + 'boolean', + ]; + + /** + * Available categories for knowledge base entries. + */ + public static function getCategories(): array + { + return [ + 'company_info' => 'Informações da Empresa', + 'products' => 'Produtos', + 'services' => 'Serviços', + 'faq' => 'Perguntas Frequentes', + 'general' => 'Geral', + ]; + } + + /** + * Scope to get only active entries. + */ + public function scopeActive($query) + { + return $query->where('active', true); + } + + /** + * Get all knowledge as a formatted text for the system prompt. + */ + public static function getFormattedKnowledge(): string + { + $entries = self::active()->orderBy('category')->orderBy('title')->get(); + + if ($entries->isEmpty()) { + return ''; + } + + $grouped = $entries->groupBy('category'); + $categories = self::getCategories(); + $output = ''; + + foreach ($grouped as $category => $items) { + $label = $categories[$category] ?? $category; + $output .= "\n## {$label}\n"; + foreach ($items as $item) { + $output .= "- **{$item->title}**: {$item->content}\n"; + if ($item->restrictions) { + $output .= " - ⚠️ Restrição: {$item->restrictions}\n"; + } + } + } + + return $output; + } +} diff --git a/app/Modules/Chatbot/Models/ChatbotMessage.php b/app/Modules/Chatbot/Models/ChatbotMessage.php new file mode 100755 index 0000000..f47c694 --- /dev/null +++ b/app/Modules/Chatbot/Models/ChatbotMessage.php @@ -0,0 +1,26 @@ +belongsTo(ChatbotConversation::class, 'conversation_id'); + } +} diff --git a/app/Modules/Chatbot/Models/ChatbotRule.php b/app/Modules/Chatbot/Models/ChatbotRule.php new file mode 100755 index 0000000..dd8cced --- /dev/null +++ b/app/Modules/Chatbot/Models/ChatbotRule.php @@ -0,0 +1,52 @@ + 'boolean', + 'order' => 'integer', + ]; + + /** + * Scope to get only active rules. + */ + public function scopeActive($query) + { + return $query->where('active', true); + } + + /** + * Get all active rules as a formatted text block for the system prompt. + * + * Returns an empty string when no active rule exists, so the prompt + * stays clean and the AI behaves as before. + */ + public static function getFormattedRules(): string + { + $rules = self::active()->orderBy('order')->orderBy('id')->get(); + + if ($rules->isEmpty()) { + return ''; + } + + $output = ''; + foreach ($rules as $rule) { + $output .= "- **{$rule->title}**: {$rule->content}\n"; + } + + return trim($output); + } +} diff --git a/app/Modules/Chatbot/Notifications/ChatbotConversationNotification.php b/app/Modules/Chatbot/Notifications/ChatbotConversationNotification.php new file mode 100755 index 0000000..15f5d28 --- /dev/null +++ b/app/Modules/Chatbot/Notifications/ChatbotConversationNotification.php @@ -0,0 +1,78 @@ +conversation = $conversation; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + + $prefs = $notifiable->preferences ?? []; + + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject('Nova Conversa do Chatbot - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Um visitante iniciou uma conversa no chatbot do site.') + ->line('**Nome:** ' . ($this->conversation->visitor_name ?? 'Não informado')) + ->line('**Telefone:** ' . ($this->conversation->visitor_phone ?? 'Não informado')) + ->line('**Cidade:** ' . ($this->conversation->visitor_city ?? 'Não informado')) + ->action('Ver Conversa', route('admin.chatbot.conversation.show', $this->conversation->id)) + ->line('Acesse o painel para ver o histórico completo da conversa.'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + 'conversation_id' => $this->conversation->id, + 'name' => $this->conversation->visitor_name ?? 'Visitante', + 'email' => $this->conversation->visitor_phone ?? '', + 'message' => 'Nova conversa no chatbot: ' . ($this->conversation->visitor_name ?? 'Visitante') . ' de ' . ($this->conversation->visitor_city ?? '?'), + 'link' => route('admin.chatbot.conversation.show', $this->conversation->id) + ]; + } +} diff --git a/app/Modules/Chatbot/Providers/ChatbotServiceProvider.php b/app/Modules/Chatbot/Providers/ChatbotServiceProvider.php new file mode 100755 index 0000000..3fc9562 --- /dev/null +++ b/app/Modules/Chatbot/Providers/ChatbotServiceProvider.php @@ -0,0 +1,45 @@ +loadViewsFrom( + __DIR__ . '/../resources/views', + 'chatbot' + ); + + // Register module routes + Route::middleware(['web']) + ->group(__DIR__ . '/../routes/web.php'); + } +} diff --git a/app/Modules/Chatbot/Services/LlamaCppService.php b/app/Modules/Chatbot/Services/LlamaCppService.php new file mode 100755 index 0000000..21e14f6 --- /dev/null +++ b/app/Modules/Chatbot/Services/LlamaCppService.php @@ -0,0 +1,175 @@ + string, 'tokens_used' => int|null] + */ + public function chat(array $messages, ?ChatbotConversation $conversation = null): array + { + $baseUrl = Setting::get('chatbot_llamacpp_base_url', 'http://172.17.0.1:8080'); + $model = Setting::get('chatbot_llamacpp_model', ''); + + if (empty($baseUrl)) { + Log::warning('Chatbot: llama.cpp base URL not configured.'); + return [ + 'content' => 'Desculpe, o assistente virtual local não está configurado corretamente. Por favor, entre em contato com a empresa.', + 'tokens_used' => null, + ]; + } + + $systemPrompt = $this->buildSystemPrompt($conversation); + + $payload = [ + 'messages' => array_merge( + [['role' => 'system', 'content' => $systemPrompt]], + $messages + ), + 'max_tokens' => (int) Setting::get('chatbot_max_tokens', 500), + 'temperature' => (float) Setting::get('chatbot_temperature', 0.7), + 'stream' => false, + ]; + + // Add model name if specified (some llama.cpp setups use it) + if (!empty($model)) { + $payload['model'] = $model; + } + + $url = rtrim($baseUrl, '/') . '/v1/chat/completions'; + + try { + $response = Http::timeout(120) + ->withHeaders([ + 'Content-Type' => 'application/json', + ]) + ->post($url, $payload); + + if ($response->successful()) { + $data = $response->json(); + $tokensUsed = $data['usage']['total_tokens'] ?? null; + + // Update cumulative token counter + if ($tokensUsed) { + $currentTotal = (int) Setting::get('chatbot_total_tokens_used', 0); + Setting::set('chatbot_total_tokens_used', $currentTotal + $tokensUsed, 'chatbot'); + } + + return [ + 'content' => $data['choices'][0]['message']['content'] ?? 'Desculpe, não consegui processar sua mensagem.', + 'tokens_used' => $tokensUsed, + ]; + } + + Log::error('Chatbot: llama.cpp API error', [ + 'status' => $response->status(), + 'body' => $response->body(), + ]); + + return [ + 'content' => 'Desculpe, estou com dificuldades para responder no momento. Tente novamente em instantes.', + 'tokens_used' => null, + ]; + + } catch (\Exception $e) { + Log::error('Chatbot: Exception calling llama.cpp API', [ + 'message' => $e->getMessage(), + 'url' => $url, + ]); + + return [ + 'content' => 'Desculpe, ocorreu um erro de conexão com o servidor local. Tente novamente em instantes.', + 'tokens_used' => null, + ]; + } + } + + /** + * Build the system prompt with company knowledge. + */ + protected function buildSystemPrompt(?ChatbotConversation $conversation = null): string + { + $companyName = Setting::get('company_name', 'Nossa Empresa'); + $companyEmail = Setting::get('company_email'); + $companyPhone = Setting::get('company_phone'); + $companyAddress = Setting::get('company_address'); + $companyHours = Setting::get('company_hours'); + + $customInstructions = Setting::get('chatbot_custom_instructions', ''); + + $prompt = "Você é um assistente virtual de atendimento ao cliente da empresa **{$companyName}**.\n\n"; + $prompt .= "## Regras Importantes:\n"; + $prompt .= "- Responda APENAS sobre a empresa {$companyName}, seus produtos, serviços e informações relacionadas.\n"; + $prompt .= "- Seja sempre educado, profissional e prestativo.\n"; + $prompt .= "- Responda em português do Brasil.\n"; + $prompt .= "- Se não souber a resposta, diga que não possui essa informação e sugira entrar em contato pelo telefone ou e-mail da empresa.\n"; + $prompt .= "- NUNCA invente informações que não foram fornecidas na base de conhecimento.\n"; + $prompt .= "- NUNCA forneça informações sobre concorrentes ou outras empresas.\n"; + $prompt .= "- NUNCA recomende ou sugira que o visitante faça pesquisas na internet (ex: \"pesquise no Google\", \"busque online\", \"procure no site\", \"acesse o link\", etc.). Toda informação deve ser fornecida diretamente por você com base exclusivamente no conteúdo da empresa {$companyName}.\n"; + $prompt .= "- NUNCA forneça links, URLs ou endereços de sites externos, redes sociais ou buscadores. Utilize apenas os canais de contato oficiais da empresa (telefone, e-mail, WhatsApp ou endereço físico) listados abaixo.\n"; + $prompt .= "- NUNCA diga frases como \"você pode pesquisar\", \"procure saber\", \"verifique na internet\", \"consulte online\" ou qualquer variação que incentive o visitante a buscar informação fora deste chat.\n"; + $prompt .= "- Mantenha as respostas concisas e objetivas.\n\n"; + + if ($companyEmail) { + $prompt .= "- E-mail de contato: {$companyEmail}\n"; + } + if ($companyPhone) { + $prompt .= "- Telefone de contato: {$companyPhone}\n"; + } + if ($companyAddress) { + $prompt .= "- Endereço: {$companyAddress}\n"; + } + if ($companyHours) { + $prompt .= "- Horário de funcionamento: {$companyHours}\n"; + } + + $prompt .= "\n"; + + // Add visitor info if available + if ($conversation && $conversation->visitor_name) { + $prompt .= "## Dados do Visitante:\n"; + $prompt .= "- **Nome**: {$conversation->visitor_name}\n"; + if ($conversation->visitor_phone) { + $prompt .= "- **Telefone**: {$conversation->visitor_phone}\n"; + } + if ($conversation->visitor_city) { + $prompt .= "- **Cidade**: {$conversation->visitor_city}\n"; + } + $prompt .= "\nSempre cumprimente o visitante pelo nome e personalize o atendimento.\n"; + $prompt .= "\n"; + } + + // Add knowledge base entries + $knowledge = ChatbotKnowledge::getFormattedKnowledge(); + if ($knowledge) { + $prompt .= "## Base de Conhecimento da Empresa:\n"; + $prompt .= $knowledge; + } + + // Add configurable AI rules (admin-defined behavior constraints) + $rules = ChatbotRule::getFormattedRules(); + if ($rules) { + $prompt .= "\n## Regras Obrigatórias de Comportamento:\n"; + $prompt .= "As regras abaixo foram definidas pela administração e DEVEM ser seguidas rigorosamente em TODAS as respostas:\n"; + $prompt .= $rules . "\n"; + } + + // Add custom instructions + if ($customInstructions) { + $prompt .= "\n## Instruções Adicionais:\n{$customInstructions}\n"; + } + + return $prompt; + } +} diff --git a/app/Modules/Chatbot/Services/OpenRouterService.php b/app/Modules/Chatbot/Services/OpenRouterService.php new file mode 100755 index 0000000..56936c3 --- /dev/null +++ b/app/Modules/Chatbot/Services/OpenRouterService.php @@ -0,0 +1,168 @@ + string, 'tokens_used' => int|null] + */ + public function chat(array $messages, ?ChatbotConversation $conversation = null): array + { + $apiKey = Setting::get('chatbot_openrouter_api_key'); + $model = Setting::get('chatbot_openrouter_model', 'openai/gpt-3.5-turbo'); + + if (empty($apiKey)) { + Log::warning('Chatbot: OpenRouter API key not configured.'); + return [ + 'content' => 'Desculpe, o assistente virtual não está configurado corretamente. Por favor, entre em contato com a empresa.', + 'tokens_used' => null, + ]; + } + + $systemPrompt = $this->buildSystemPrompt($conversation); + + $payload = [ + 'model' => $model, + 'messages' => array_merge( + [['role' => 'system', 'content' => $systemPrompt]], + $messages + ), + 'max_tokens' => (int) Setting::get('chatbot_max_tokens', 500), + 'temperature' => (float) Setting::get('chatbot_temperature', 0.7), + ]; + + try { + $response = Http::withHeaders([ + 'Authorization' => 'Bearer ' . $apiKey, + 'Content-Type' => 'application/json', + 'HTTP-Referer' => config('app.url'), + 'X-Title' => 'SysSiteCMS Chatbot', + ])->post('https://openrouter.ai/api/v1/chat/completions', $payload); + + if ($response->successful()) { + $data = $response->json(); + $tokensUsed = $data['usage']['total_tokens'] ?? null; + + // Update cumulative token counter + if ($tokensUsed) { + $currentTotal = (int) Setting::get('chatbot_total_tokens_used', 0); + Setting::set('chatbot_total_tokens_used', $currentTotal + $tokensUsed, 'chatbot'); + } + + return [ + 'content' => $data['choices'][0]['message']['content'] ?? 'Desculpe, não consegui processar sua mensagem.', + 'tokens_used' => $tokensUsed, + ]; + } + + Log::error('Chatbot: OpenRouter API error', [ + 'status' => $response->status(), + 'body' => $response->body(), + ]); + + return [ + 'content' => 'Desculpe, estou com dificuldades para responder no momento. Tente novamente em instantes.', + 'tokens_used' => null, + ]; + + } catch (\Exception $e) { + Log::error('Chatbot: Exception calling OpenRouter API', [ + 'message' => $e->getMessage(), + ]); + + return [ + 'content' => 'Desculpe, ocorreu um erro de conexão. Tente novamente em instantes.', + 'tokens_used' => null, + ]; + } + } + + /** + * Build the system prompt with company knowledge. + */ + protected function buildSystemPrompt(?ChatbotConversation $conversation = null): string + { + $companyName = Setting::get('company_name', 'Nossa Empresa'); + $companyEmail = Setting::get('company_email'); + $companyPhone = Setting::get('company_phone'); + $companyAddress = Setting::get('company_address'); + $companyHours = Setting::get('company_hours'); + + $customInstructions = Setting::get('chatbot_custom_instructions', ''); + + $prompt = "Você é um assistente virtual de atendimento ao cliente da empresa **{$companyName}**.\n\n"; + $prompt .= "## Regras Importantes:\n"; + $prompt .= "- Responda APENAS sobre a empresa {$companyName}, seus produtos, serviços e informações relacionadas.\n"; + $prompt .= "- Seja sempre educado, profissional e prestativo.\n"; + $prompt .= "- Responda em português do Brasil.\n"; + $prompt .= "- Se não souber a resposta, diga que não possui essa informação e sugira entrar em contato pelo telefone ou e-mail da empresa.\n"; + $prompt .= "- NUNCA invente informações que não foram fornecidas na base de conhecimento.\n"; + $prompt .= "- NUNCA forneça informações sobre concorrentes ou outras empresas.\n"; + $prompt .= "- NUNCA recomende ou sugira que o visitante faça pesquisas na internet (ex: \"pesquise no Google\", \"busque online\", \"procure no site\", \"acesse o link\", etc.). Toda informação deve ser fornecida diretamente por você com base exclusivamente no conteúdo da empresa {$companyName}.\n"; + $prompt .= "- NUNCA forneça links, URLs ou endereços de sites externos, redes sociais ou buscadores. Utilize apenas os canais de contato oficiais da empresa (telefone, e-mail, WhatsApp ou endereço físico) listados abaixo.\n"; + $prompt .= "- NUNCA diga frases como \"você pode pesquisar\", \"procure saber\", \"verifique na internet\", \"consulte online\" ou qualquer variação que incentive o visitante a buscar informação fora deste chat.\n"; + $prompt .= "- Mantenha as respostas concisas e objetivas.\n\n"; + + if ($companyEmail) { + $prompt .= "- E-mail de contato: {$companyEmail}\n"; + } + if ($companyPhone) { + $prompt .= "- Telefone de contato: {$companyPhone}\n"; + } + if ($companyAddress) { + $prompt .= "- Endereço: {$companyAddress}\n"; + } + if ($companyHours) { + $prompt .= "- Horário de funcionamento: {$companyHours}\n"; + } + + $prompt .= "\n"; + + // Add visitor info if available + if ($conversation && $conversation->visitor_name) { + $prompt .= "## Dados do Visitante:\n"; + $prompt .= "- **Nome**: {$conversation->visitor_name}\n"; + if ($conversation->visitor_phone) { + $prompt .= "- **Telefone**: {$conversation->visitor_phone}\n"; + } + if ($conversation->visitor_city) { + $prompt .= "- **Cidade**: {$conversation->visitor_city}\n"; + } + $prompt .= "\nSempre cumprimente o visitante pelo nome e personalize o atendimento.\n"; + $prompt .= "\n"; + } + + // Add knowledge base entries + $knowledge = ChatbotKnowledge::getFormattedKnowledge(); + if ($knowledge) { + $prompt .= "## Base de Conhecimento da Empresa:\n"; + $prompt .= $knowledge; + } + + // Add configurable AI rules (admin-defined behavior constraints) + $rules = ChatbotRule::getFormattedRules(); + if ($rules) { + $prompt .= "\n## Regras Obrigatórias de Comportamento:\n"; + $prompt .= "As regras abaixo foram definidas pela administração e DEVEM ser seguidas rigorosamente em TODAS as respostas:\n"; + $prompt .= $rules . "\n"; + } + + // Add custom instructions + if ($customInstructions) { + $prompt .= "\n## Instruções Adicionais:\n{$customInstructions}\n"; + } + + return $prompt; + } +} diff --git a/app/Modules/Chatbot/resources/views/admin/conversation-show.blade.php b/app/Modules/Chatbot/resources/views/admin/conversation-show.blade.php new file mode 100755 index 0000000..411d4f1 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/conversation-show.blade.php @@ -0,0 +1,126 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Conversa com ' . $conversation->visitor_name) + +@section('content') +
+ +
+

+ Conversa com {{ $conversation->visitor_name }} +

+ + Voltar + +
+ + +
+
+
+ Dados do Visitante +
+
+
+
+
+

Nome

+

{{ $conversation->visitor_name }}

+
+
+

Telefone

+

+ @if($conversation->visitor_phone) + {{ $conversation->visitor_phone }} + + WhatsApp + + @else + — + @endif +

+
+
+

Cidade

+

{{ $conversation->visitor_city ?? '—' }}

+
+
+

Status

+

+ @if($conversation->status === 'active') + + Ativa + + @else + + Finalizada + + @if($conversation->ended_at) +
{{ $conversation->ended_at->format('d/m/Y H:i:s') }} + @endif + @endif +

+
+
+
+
+

Data de Início

+

{{ $conversation->created_at->format('d/m/Y H:i:s') }}

+
+
+

Total de Mensagens

+

{{ $conversation->messages->count() }}

+
+
+

Avaliação do Atendimento

+

+ @if($conversation->rating) + + {{ str_repeat('★', $conversation->rating) }}{{ str_repeat('☆', 5 - $conversation->rating) }} + + ({{ $conversation->rating }}/5) + @if($conversation->rating_comment) +
"{{ $conversation->rating_comment }}" + @endif + @else + Não avaliada + @endif +

+
+
+
+
+ + +
+
+
+ Histórico de Mensagens +
+
+
+
+ @forelse($conversation->messages as $message) +
+
+
+ @if($message->role === 'user') + {{ $conversation->visitor_name }} + @elseif($message->role === 'assistant') + Assistente + @else + Sistema + @endif + {{ $message->created_at->format('H:i') }} +
+
{{ $message->content }}
+
+
+ @empty +

Nenhuma mensagem nesta conversa.

+ @endforelse +
+
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/conversations.blade.php b/app/Modules/Chatbot/resources/views/admin/conversations.blade.php new file mode 100755 index 0000000..7d73462 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/conversations.blade.php @@ -0,0 +1,139 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Conversas') + +@section('content') +
+ +
+

+ Chatbot - Conversas +

+ +
+ + @if(session('success')) + + @endif + + +
+
+
Contatos Recebidos ({{ $conversations->total() }} conversas)
+
+
+ @if($conversations->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($conversations as $conv) + + + + + + + + + + + @endforeach + +
NomeTelefoneCidadeStatusAvaliaçãoMensagensDataAções
+ {{ $conv->visitor_name }} + + @if($conv->visitor_phone) + + {{ $conv->visitor_phone }} + + @else + — + @endif + + @if($conv->visitor_city) + {{ $conv->visitor_city }} + @else + — + @endif + + @if($conv->status === 'active') + + Ativa + + @else + + Finalizada + + @endif + + @if($conv->rating) + + {{ str_repeat('★', $conv->rating) }}{{ str_repeat('☆', 5 - $conv->rating) }} + + @if($conv->rating_comment) +
+ {{ Str::limit($conv->rating_comment, 40) }} + + @endif + @else + — + @endif +
+ {{ $conv->messages->count() }} msgs + {{ $conv->created_at->format('d/m/Y H:i') }} + + + + @if($conv->visitor_phone) + + + + @endif +
+ @csrf + @method('DELETE') + +
+
+
+ +
+ {{ $conversations->withQueryString()->links() }} +
+ @else +
+ +
Nenhuma conversa registrada
+

As conversas aparecerão aqui quando os visitantes iniciarem um chat.

+
+ @endif +
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/create.blade.php b/app/Modules/Chatbot/resources/views/admin/create.blade.php new file mode 100755 index 0000000..f45fcfd --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/create.blade.php @@ -0,0 +1,97 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Nova Informação') + +@section('content') +
+ +
+

+ Nova Informação para o Chatbot +

+ + Voltar + +
+ + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
Dados da Informação
+
+
+
+ @csrf + +
+ +
+ + Um título descritivo para identificar esta informação. +
+
+ +
+ +
+ +
+
+ +
+ +
+ + Descreva a informação em detalhes. Quanto mais detalhada, melhor será a resposta do assistente. +
+
+ +
+ +
+ + O que a IA NÃO deve fazer ou informar sobre este tópico. Deixe vazio se não houver restrições. +
+
+ +
+
+
+ + +
+
+
+ +
+
+ + Cancelar +
+
+
+
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/edit.blade.php b/app/Modules/Chatbot/resources/views/admin/edit.blade.php new file mode 100755 index 0000000..90fb595 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/edit.blade.php @@ -0,0 +1,95 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Editar Informação') + +@section('content') +
+ +
+

+ Editar Informação do Chatbot +

+ + Voltar + +
+ + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+
+
Editar: {{ $entry->title }}
+
+
+
+ @csrf + @method('PUT') + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + O que a IA NÃO deve fazer ou informar sobre este tópico. Deixe vazio se não houver restrições. +
+
+ +
+
+
+ active) ? 'checked' : '' }}> + +
+
+
+ +
+
+ + Cancelar +
+
+
+
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/index.blade.php b/app/Modules/Chatbot/resources/views/admin/index.blade.php new file mode 100755 index 0000000..0915e89 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/index.blade.php @@ -0,0 +1,126 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Base de Conhecimento') + +@section('content') +
+ +
+

+ Chatbot - Base de Conhecimento +

+ +
+ + @if(session('success')) + + @endif + + +
+
+
Informações do Chatbot
+
+
+

+ + Adicione informações sobre a empresa, produtos e serviços para que o assistente virtual possa responder aos visitantes. + Quanto mais detalhada for a base de conhecimento, melhores serão as respostas. +

+
+
+ + +
+
+
Base de Conhecimento ({{ $knowledgeEntries->total() }} entradas)
+
+
+ @if($knowledgeEntries->count() > 0) +
+ + + + + + + + + + + + + @foreach($knowledgeEntries as $entry) + + + + + + + + + @endforeach + +
TítuloCategoriaConteúdoRestriçõesStatusAções
{{ $entry->title }} + + {{ $categories[$entry->category] ?? $entry->category }} + + + {{ Str::limit($entry->content, 80) }} + + @if($entry->restrictions) + + {{ Str::limit($entry->restrictions, 50) }} + + @else + — + @endif + + @if($entry->active) + Ativo + @else + Inativo + @endif + + + + +
+ @csrf + @method('DELETE') + +
+
+
+ +
+ {{ $knowledgeEntries->withQueryString()->links() }} +
+ @else +
+ +
Nenhuma informação cadastrada
+

Adicione informações sobre a empresa para o assistente virtual.

+ + Adicionar Primeira Informação + +
+ @endif +
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/rules/create.blade.php b/app/Modules/Chatbot/resources/views/admin/rules/create.blade.php new file mode 100755 index 0000000..6e4b1d2 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/rules/create.blade.php @@ -0,0 +1,119 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Nova Regra') + +@section('content') +
+ +
+

+ Nova Regra para a IA +

+ + Voltar + +
+ + @if($errors->any()) + + @endif + +
+
+
Dados da Regra
+
+
+
+ @csrf + +
+ +
+ + Nome curto para identificar a regra na listagem. +
+
+ +
+ +
+ + A instrução completa que será enviada à IA. Máximo de 2000 caracteres. +
+
+ +
+ +
+ + Define a prioridade da regra (menor número = maior prioridade). Padrão: 0. +
+
+ +
+ +
+
+ + +
+
+
+ +
+
+ + Cancelar +
+
+
+
+
+ + +
+
+
Exemplos de Regras Úteis
+
+
+
+
+

🎯 Direcionar para contato humano:

+

"Sempre que o visitante tiver uma dúvida complexa ou reclamação, sugira entrar em contato pelo telefone (XX) XXXX-XXXX."

+
+
+

💬 Tom de voz:

+

"Use sempre linguagem formal e profissional. Trate o visitante por 'você' mas evite gírias."

+
+
+

🚫 Proibir descontos:

+

"NUNCA ofereça descontos, promova valores ou negocie preços. Indique que valores são praticados apenas pela equipe comercial."

+
+
+

📍 Atendimento regional:

+

"Atendemos apenas as cidades de Florianópolis, São José e Biguaçu. Para outras regiões, informe que não há cobertura."

+
+
+
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/rules/edit.blade.php b/app/Modules/Chatbot/resources/views/admin/rules/edit.blade.php new file mode 100755 index 0000000..de565ba --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/rules/edit.blade.php @@ -0,0 +1,91 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Editar Regra') + +@section('content') +
+ +
+

+ Editar Regra da IA +

+ + Voltar + +
+ + @if($errors->any()) + + @endif + +
+
+
Dados da Regra
+
+
+
+ @csrf + @method('PUT') + +
+ +
+ + Nome curto para identificar a regra na listagem. +
+
+ +
+ +
+ + A instrução completa que será enviada à IA. Máximo de 2000 caracteres. +
+
+ +
+ +
+ + Define a prioridade da regra (menor número = maior prioridade). +
+
+ +
+ +
+
+ active) ? 'checked' : '' }}> + +
+
+
+ +
+
+ + Cancelar +
+
+
+
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/rules/index.blade.php b/app/Modules/Chatbot/resources/views/admin/rules/index.blade.php new file mode 100755 index 0000000..4b8bc17 --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/rules/index.blade.php @@ -0,0 +1,110 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Regras da IA') + +@section('content') +
+ +
+

+ Chatbot - Regras da IA +

+ + Nova Regra + +
+ + +
+
+
Como funcionam as Regras
+

+ As regras são instruções obrigatórias que a IA deve seguir em todas as respostas. + Elas são enviadas automaticamente no início do prompt do sistema, antes das informações da base de conhecimento. + Use para definir tom de voz, proibições específicas, fluxos obrigatórios ou qualquer comportamento desejado. + Regras desativadas são ignoradas. Use o campo Ordem para definir a prioridade (menor = mais importante). +

+
+
+ + @if(session('success')) + + @endif + + +
+
+
Regras Configuradas
+
+
+ @if($rules->isEmpty()) +
+ +

Nenhuma regra configurada ainda.

+

Adicione regras para definir como a IA deve se comportar no atendimento.

+ + Adicionar Primeira Regra + +
+ @else +
+ + + + + + + + + + + + @foreach($rules as $rule) + + + + + + + + @endforeach + +
OrdemTítuloConteúdo da RegraStatusAções
+ {{ $rule->order }} + + {{ $rule->title }} + + {{ \Illuminate\Support\Str::limit($rule->content, 150) }} + + @if($rule->active) + Ativa + @else + Inativa + @endif + + + + +
+ @csrf + @method('DELETE') + +
+
+
+ +
+ {{ $rules->withQueryString()->links() }} +
+ @endif +
+
+
+@endsection diff --git a/app/Modules/Chatbot/resources/views/admin/settings.blade.php b/app/Modules/Chatbot/resources/views/admin/settings.blade.php new file mode 100755 index 0000000..bbaa3de --- /dev/null +++ b/app/Modules/Chatbot/resources/views/admin/settings.blade.php @@ -0,0 +1,296 @@ +@extends('layouts.admin') + +@section('title', 'Chatbot - Configurações') + +@section('content') +
+ +
+

+ Chatbot - Configurações +

+ + Base de Conhecimento + +
+ + @if(session('success')) + + @endif + + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + + @php + $totalTokens = (int) \App\Models\Setting::get('chatbot_total_tokens_used', 0); + $totalMessages = \App\Modules\Chatbot\Models\ChatbotMessage::where('role', 'assistant')->count(); + $totalConversations = \App\Modules\Chatbot\Models\ChatbotConversation::whereNotNull('visitor_name')->count(); + @endphp + + +
+
+
+
+
+
+
Total de Tokens Consumidos
+
{{ number_format($totalTokens, 0, ',', '.') }}
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Respostas da IA
+
{{ number_format($totalMessages, 0, ',', '.') }}
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Conversas com Visitantes
+
{{ number_format($totalConversations, 0, ',', '.') }}
+
+
+ +
+
+
+
+
+
+ +
+ @csrf + @method('POST') + + +
+
+
+ Ativação +
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+ Provedor de IA +
+
+
+
+ +
+ + Escolha entre usar a API do OpenRouter.ai na nuvem ou um servidor llama.cpp local. +
+
+
+
+ + +
+
+
+ API OpenRouter +
+
+
+
+ + O Chatbot pode utilizar a API do OpenRouter.ai para gerar respostas inteligentes. + Crie uma conta gratuita em openrouter.ai, obtenha sua chave de API e configure abaixo. +
+ +
+ +
+ + Sua chave de API do OpenRouter.ai. Mantenha este campo seguro. +
+
+ +
+ +
+ + Digite o ID do modelo (ex: meta-llama/llama-3.2-1b-instruct). Verifique os modelos disponíveis no OpenRouter.ai. +
+
+
+
+ + +
+
+
+ llama.cpp (Servidor Local) +
+
+
+
+ + Utilize um servidor llama.cpp rodando localmente para processar as respostas. + O servidor deve estar acessível com API compatível com OpenAI (/v1/chat/completions). +
+ +
+ +
+ + URL do servidor llama.cpp (ex: http://172.17.0.1:8080). O servidor deve expor a rota /v1/chat/completions. +
+
+ +
+ +
+ + Algumas configurações do llama.cpp exigem o nome do modelo. Deixe em branco se não for necessário. +
+
+
+
+ + +
+
+
+ Parâmetros da IA +
+
+
+
+ +
+ + Controla o tamanho máximo da resposta (50-2000). Recomendado: 500. +
+
+ +
+ +
+ + Controla a criatividade das respostas (0.0 = preciso, 2.0 = criativo). Recomendado: 0.7. +
+
+
+
+ + +
+
+
+ Aparência e Mensagens +
+
+
+
+ +
+ + Primeira mensagem exibida quando o visitante abre o chat. +
+
+ +
+ +
+ + Instruções extras para o comportamento do assistente. Estas são adicionadas ao prompt do sistema. +
+
+
+
+ +
+ + Cancelar +
+
+
+ + +@endsection diff --git a/app/Modules/Chatbot/resources/views/partials/chat-widget.blade.php b/app/Modules/Chatbot/resources/views/partials/chat-widget.blade.php new file mode 100755 index 0000000..19ba81b --- /dev/null +++ b/app/Modules/Chatbot/resources/views/partials/chat-widget.blade.php @@ -0,0 +1,948 @@ +@php + $chatbotEnabled = \App\Models\Setting::get('chatbot_enabled', 'off') === 'on'; +@endphp + +@if($chatbotEnabled && module_enabled('chatbot')) + + + + + +
+
+
+
Assistente Virtual
+ Online • Respostas instantâneas +
+
+ + +
+
+ + +
+

Para um atendimento personalizado, por favor preencha seus dados:

+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + + + + + + + +
+ + +@endif diff --git a/app/Modules/Chatbot/routes/web.php b/app/Modules/Chatbot/routes/web.php new file mode 100755 index 0000000..923fb14 --- /dev/null +++ b/app/Modules/Chatbot/routes/web.php @@ -0,0 +1,62 @@ +middleware('throttle:10,1') + ->name('chatbot.register'); +Route::post('/chatbot/send', [ChatbotApiController::class, 'sendMessage']) + ->middleware('throttle:30,1') + ->name('chatbot.send'); +Route::post('/chatbot/end', [ChatbotApiController::class, 'endConversation']) + ->middleware('throttle:10,1') + ->name('chatbot.end'); +Route::post('/chatbot/rate', [ChatbotApiController::class, 'rateConversation']) + ->middleware('throttle:5,1') + ->name('chatbot.rate'); +Route::get('/chatbot/status', [ChatbotApiController::class, 'getStatus']) + ->middleware('throttle:60,1') + ->name('chatbot.status'); + +/* +|-------------------------------------------------------------------------- +| Chatbot Module - Admin Routes +|-------------------------------------------------------------------------- +*/ + +Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { + // Chatbot management (permission: chatbot) + Route::middleware('permission:chatbot')->group(function () { + Route::get('/chatbot', [ChatbotController::class, 'index'])->name('chatbot.index'); + Route::get('/chatbot/criar', [ChatbotController::class, 'create'])->name('chatbot.create'); + Route::post('/chatbot', [ChatbotController::class, 'store'])->name('chatbot.store'); + Route::get('/chatbot/{chatbot_knowledge}/editar', [ChatbotController::class, 'edit'])->name('chatbot.edit'); + Route::put('/chatbot/{chatbot_knowledge}', [ChatbotController::class, 'update'])->name('chatbot.update'); + Route::delete('/chatbot/{chatbot_knowledge}', [ChatbotController::class, 'destroy'])->name('chatbot.destroy'); + + // Chatbot conversations (contacts) + Route::get('/chatbot/conversas', [ChatbotController::class, 'conversations'])->name('chatbot.conversations'); + Route::get('/chatbot/conversas/{conversation}', [ChatbotController::class, 'showConversation'])->name('chatbot.conversation.show'); + Route::delete('/chatbot/conversas/{conversation}', [ChatbotController::class, 'destroyConversation'])->name('chatbot.conversation.destroy'); + + // Chatbot settings + Route::get('/chatbot/configuracoes', [ChatbotController::class, 'settings'])->name('chatbot.settings'); + Route::post('/chatbot/configuracoes', [ChatbotController::class, 'updateSettings'])->name('chatbot.settings.update'); + + // Chatbot AI rules (configurable behavior rules) + Route::get('/chatbot/regras', [ChatbotController::class, 'rulesIndex'])->name('chatbot.rules.index'); + Route::get('/chatbot/regras/criar', [ChatbotController::class, 'rulesCreate'])->name('chatbot.rules.create'); + Route::post('/chatbot/regras', [ChatbotController::class, 'rulesStore'])->name('chatbot.rules.store'); + Route::get('/chatbot/regras/{rule}/editar', [ChatbotController::class, 'rulesEdit'])->name('chatbot.rules.edit'); + Route::put('/chatbot/regras/{rule}', [ChatbotController::class, 'rulesUpdate'])->name('chatbot.rules.update'); + Route::delete('/chatbot/regras/{rule}', [ChatbotController::class, 'rulesDestroy'])->name('chatbot.rules.destroy'); + }); +}); diff --git a/app/Modules/Products/Http/Controllers/Admin/ProductCategoryController.php b/app/Modules/Products/Http/Controllers/Admin/ProductCategoryController.php new file mode 100755 index 0000000..c96b528 --- /dev/null +++ b/app/Modules/Products/Http/Controllers/Admin/ProductCategoryController.php @@ -0,0 +1,88 @@ +paginate(10); + return view('products::admin.categories.index', compact('categories')); + } + + public function create() + { + return view('products::admin.categories.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:product_categories', + 'description' => 'nullable|string', + 'image' => 'nullable|image|max:2048', + 'active' => 'required|boolean', + 'order' => 'required|integer', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + $data['image'] = $request->file('image')->store('categories', 'public'); + } + + ProductCategory::create($data); + + return redirect()->route('admin.categories.index')->with('success', 'Categoria criada com sucesso.'); + } + + public function edit(ProductCategory $category) + { + return view('products::admin.categories.edit', compact('category')); + } + + public function update(Request $request, ProductCategory $category) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:product_categories,slug,' . $category->id, + 'description' => 'nullable|string', + 'image' => 'nullable|image|max:2048', + 'active' => 'required|boolean', + 'order' => 'required|integer', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + if ($category->image) { + Storage::disk('public')->delete($category->image); + } + $data['image'] = $request->file('image')->store('categories', 'public'); + } + + $category->update($data); + + return redirect()->route('admin.categories.index')->with('success', 'Categoria atualizada com sucesso.'); + } + + public function destroy(ProductCategory $category) + { + if ($category->products()->count() > 0) { + return redirect()->back()->with('error', 'Esta categoria possui produtos vinculados e não pode ser excluída.'); + } + + if ($category->image) { + Storage::disk('public')->delete($category->image); + } + + $category->delete(); + return redirect()->route('admin.categories.index')->with('success', 'Categoria excluída com sucesso.'); + } +} diff --git a/app/Modules/Products/Http/Controllers/Admin/ProductController.php b/app/Modules/Products/Http/Controllers/Admin/ProductController.php new file mode 100755 index 0000000..6f1d2eb --- /dev/null +++ b/app/Modules/Products/Http/Controllers/Admin/ProductController.php @@ -0,0 +1,100 @@ +latest()->paginate(10); + return view('products::admin.products.index', compact('products')); + } + + public function create() + { + $categories = ProductCategory::where('active', true)->orderBy('order')->get(); + return view('products::admin.products.create', compact('categories')); + } + + public function store(Request $request) + { + $request->validate([ + 'category_id' => 'nullable|exists:product_categories,id', + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:products', + 'description' => 'nullable|string', + 'price' => 'nullable|numeric', + 'image' => 'nullable|image|max:2048', + 'active' => 'required|boolean', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + $data['image'] = $request->file('image')->store('products', 'public'); + } + + $product = Product::create($data); + + // Notify administrators + $admins = User::where('user_type', 'Admin')->get(); + Notification::send($admins, new ProductActionNotification($product, Auth::user(), 'created')); + + return redirect()->route('admin.products.index')->with('success', 'Produto criado com sucesso.'); + } + + public function edit(Product $product) + { + $categories = ProductCategory::where('active', true)->orderBy('order')->get(); + return view('products::admin.products.edit', compact('product', 'categories')); + } + + public function update(Request $request, Product $product) + { + $request->validate([ + 'category_id' => 'nullable|exists:product_categories,id', + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:products,slug,' . $product->id, + 'description' => 'nullable|string', + 'price' => 'nullable|numeric', + 'image' => 'nullable|image|max:2048', + 'active' => 'required|boolean', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + if ($product->image) { + Storage::disk('public')->delete($product->image); + } + $data['image'] = $request->file('image')->store('products', 'public'); + } + + $product->update($data); + + // Notify administrators + $admins = User::where('user_type', 'Admin')->get(); + Notification::send($admins, new ProductActionNotification($product, Auth::user(), 'updated')); + + return redirect()->route('admin.products.index')->with('success', 'Produto atualizado com sucesso.'); + } + + public function destroy(Product $product) + { + if ($product->image) { + Storage::disk('public')->delete($product->image); + } + $product->delete(); + return redirect()->route('admin.products.index')->with('success', 'Produto excluído com sucesso.'); + } +} diff --git a/app/Modules/Products/Http/Controllers/ProductPageController.php b/app/Modules/Products/Http/Controllers/ProductPageController.php new file mode 100755 index 0000000..9fe49f3 --- /dev/null +++ b/app/Modules/Products/Http/Controllers/ProductPageController.php @@ -0,0 +1,41 @@ +where('active', true)->firstOrFail(); + + $categories = ProductCategory::where('active', true) + ->whereHas('products', function ($q) { + $q->where('active', true); + }) + ->orderBy('order') + ->get(); + + $query = Product::where('active', true); + + if ($request->has('categoria') && $request->categoria != '') { + $query->whereHas('category', function ($q) use ($request) { + $q->where('slug', $request->categoria); + }); + } + + $products = $query->latest()->get(); + + $currentCategory = null; + if ($request->has('categoria') && $request->categoria != '') { + $currentCategory = ProductCategory::where('slug', $request->categoria)->firstOrFail(); + } + + return view('products::produtos', compact('page', 'products', 'categories', 'currentCategory')); + } +} diff --git a/app/Modules/Products/Models/Product.php b/app/Modules/Products/Models/Product.php new file mode 100755 index 0000000..32e1a9a --- /dev/null +++ b/app/Modules/Products/Models/Product.php @@ -0,0 +1,16 @@ +belongsTo(ProductCategory::class, 'category_id'); + } +} diff --git a/app/Modules/Products/Models/ProductCategory.php b/app/Modules/Products/Models/ProductCategory.php new file mode 100755 index 0000000..0da2c2b --- /dev/null +++ b/app/Modules/Products/Models/ProductCategory.php @@ -0,0 +1,16 @@ +hasMany(Product::class, 'category_id'); + } +} diff --git a/app/Modules/Products/Notifications/ProductActionNotification.php b/app/Modules/Products/Notifications/ProductActionNotification.php new file mode 100755 index 0000000..09320cd --- /dev/null +++ b/app/Modules/Products/Notifications/ProductActionNotification.php @@ -0,0 +1,88 @@ +product = $product; + $this->user = $user; + $this->action = $action; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + $prefs = $notifiable->preferences ?? []; + + // If admin wants to receive product change notifications + if ($prefs['notifications_product_actions'] ?? true) { + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + $actionText = $this->action === 'created' ? 'criado' : 'atualizado'; + + return (new MailMessage) + ->subject('Produto ' . ucfirst($actionText) . ' - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Um produto do site foi ' . $actionText . ' por ' . $this->user->name . '.') + ->line('**Nome do Produto:** ' . $this->product->name) + ->line('**Preço:** R$ ' . number_format($this->product->price, 2, ',', '.')) + ->action('Gerenciar Produtos', route('admin.products.index')) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + $actionText = $this->action === 'created' ? 'criado' : 'atualizado'; + + return [ + 'product_id' => $this->product->id, + 'name' => $this->user->name, + 'email' => $this->user->email, + 'action' => $this->action, + 'message' => "Produto '{$this->product->name}' foi {$actionText}.", + 'link' => route('admin.products.edit', $this->product->id) + ]; + } +} diff --git a/app/Modules/Products/Providers/ProductsServiceProvider.php b/app/Modules/Products/Providers/ProductsServiceProvider.php new file mode 100755 index 0000000..66905f4 --- /dev/null +++ b/app/Modules/Products/Providers/ProductsServiceProvider.php @@ -0,0 +1,45 @@ +loadViewsFrom( + __DIR__ . '/../resources/views', + 'products' + ); + + // Register module routes + Route::middleware(['web']) + ->group(__DIR__ . '/../routes/web.php'); + } +} diff --git a/app/Modules/Products/resources/views/admin/categories/create.blade.php b/app/Modules/Products/resources/views/admin/categories/create.blade.php new file mode 100755 index 0000000..81e48f2 --- /dev/null +++ b/app/Modules/Products/resources/views/admin/categories/create.blade.php @@ -0,0 +1,102 @@ +@extends('layouts.admin') + +@section('title', 'Nova Categoria') + +@section('content') +
+

Nova Categoria

+ + Voltar + +
+ +
+
+
Informações da Categoria
+
+
+
+ @csrf + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Products/resources/views/admin/categories/edit.blade.php b/app/Modules/Products/resources/views/admin/categories/edit.blade.php new file mode 100755 index 0000000..d2a1e4e --- /dev/null +++ b/app/Modules/Products/resources/views/admin/categories/edit.blade.php @@ -0,0 +1,109 @@ +@extends('layouts.admin') + +@section('title', 'Editar Categoria') + +@section('content') +
+

Editar Categoria: {{ $category->name }}

+ + Voltar + +
+ +
+
+
Informações da Categoria
+
+
+
+ @csrf + @method('PUT') + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + @if($category->image) +
+ +
+ @endif + + Deixe em branco para manter a imagem atual. + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Products/resources/views/admin/categories/index.blade.php b/app/Modules/Products/resources/views/admin/categories/index.blade.php new file mode 100755 index 0000000..83acf25 --- /dev/null +++ b/app/Modules/Products/resources/views/admin/categories/index.blade.php @@ -0,0 +1,78 @@ +@extends('layouts.admin') + +@section('title', 'Categorias de Produtos') + +@section('content') +
+

Categorias de Produtos

+ + Nova Categoria + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +@if(session('error')) +
{{ session('error') }}
+@endif + +
+
+
Lista de Categorias
+
+
+
+ + + + + + + + + + + + + @foreach($categories as $category) + + + + + + + + + @endforeach + +
OrdemImagemNomeSlugStatusAções
{{ $category->order }} + @if($category->image) + + @else + Sem foto + @endif + {{ $category->name }}{{ $category->slug }} + + {{ $category->active ? 'Ativa' : 'Inativa' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $categories->links() }} +
+
+
+@endsection diff --git a/app/Modules/Products/resources/views/admin/products/create.blade.php b/app/Modules/Products/resources/views/admin/products/create.blade.php new file mode 100755 index 0000000..8bdb621 --- /dev/null +++ b/app/Modules/Products/resources/views/admin/products/create.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.admin') + +@section('title', 'Novo Produto') + +@section('content') +
+

Novo Produto

+ + Voltar + +
+ +
+
+
Informações do Produto
+
+
+
+ @csrf + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + + @error('category_id')
{{ $message }}
@enderror +
+ +
+ + + @error('price')
{{ $message }}
@enderror +
+ +
+ + + @error('image')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Products/resources/views/admin/products/edit.blade.php b/app/Modules/Products/resources/views/admin/products/edit.blade.php new file mode 100755 index 0000000..480670c --- /dev/null +++ b/app/Modules/Products/resources/views/admin/products/edit.blade.php @@ -0,0 +1,123 @@ +@extends('layouts.admin') + +@section('title', 'Editar Produto') + +@section('content') +
+

Editar Produto: {{ $product->name }}

+ + Voltar + +
+ +
+
+
Informações do Produto
+
+
+
+ @csrf + @method('PUT') + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + + @error('category_id')
{{ $message }}
@enderror +
+ +
+ + + @error('price')
{{ $message }}
@enderror +
+ +
+ + @if($product->image) +
+ +
+ @endif + + Deixe em branco para manter a imagem atual. + @error('image')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Products/resources/views/admin/products/index.blade.php b/app/Modules/Products/resources/views/admin/products/index.blade.php new file mode 100755 index 0000000..43de3b8 --- /dev/null +++ b/app/Modules/Products/resources/views/admin/products/index.blade.php @@ -0,0 +1,80 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Produtos') + +@section('content') +
+

Gerenciamento de Produtos

+ + Novo Produto + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Produtos
+
+
+
+ + + + + + + + + + + + + @foreach($products as $product) + + + + + + + + + @endforeach + +
ImagemNomeCategoriaPreçoStatusAções
+ @if($product->image) + + @else + Sem foto + @endif + {{ $product->name }} + @if($product->category) + {{ $product->category->name }} + @else + Sem Categoria + @endif + R$ {{ number_format($product->price, 2, ',', '.') }} + + {{ $product->active ? 'Ativo' : 'Inativo' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $products->links() }} +
+
+
+@endsection diff --git a/app/Modules/Products/resources/views/produtos.blade.php b/app/Modules/Products/resources/views/produtos.blade.php new file mode 100755 index 0000000..c8aa971 --- /dev/null +++ b/app/Modules/Products/resources/views/produtos.blade.php @@ -0,0 +1,147 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') +
+
+

{{ $page->title }}

+
{!! $page->content !!}
+
+ + + @if($categories->count() > 0) +
+
+
+ + Todos + + @foreach($categories as $category) + + {{ $category->name }} + + @endforeach +
+
+
+ @endif + + + @if($currentCategory) +
+ @if($currentCategory->image) +
+ {{ $currentCategory->name }} +
+ @endif +
+

{{ $currentCategory->name }}

+ @if($currentCategory->description) +
{!! $currentCategory->description !!}
+ @endif +
+
+ @endif + +
+ @forelse($products as $product) +
+
+
+ @if($product->image) + {{ $product->name }} + @else +
+ +
+ @endif + @if($product->price) +
+ R$ {{ number_format($product->price, 2, ',', '.') }} +
+ @endif +
+
+
{{ $product->name }}
+
+ {!! Str::limit(strip_tags($product->description), 100) !!} +
+
+ +
+
+
+
+ + + + @empty +
+ +

Nenhum produto cadastrado no momento.

+
+ @endforelse +
+
+@endsection + +@push('css') + +@endpush diff --git a/app/Modules/Products/routes/web.php b/app/Modules/Products/routes/web.php new file mode 100755 index 0000000..84b35ad --- /dev/null +++ b/app/Modules/Products/routes/web.php @@ -0,0 +1,39 @@ +name('produtos'); + +/* +|-------------------------------------------------------------------------- +| Products Module - Admin Routes +|-------------------------------------------------------------------------- +*/ + +Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { + // Products management (permission: products) + Route::middleware('permission:products')->group(function () { + Route::get('/produtos/categorias', [ProductCategoryController::class, 'index'])->name('categories.index'); + Route::get('/produtos/categorias/criar', [ProductCategoryController::class, 'create'])->name('categories.create'); + Route::post('/produtos/categorias', [ProductCategoryController::class, 'store'])->name('categories.store'); + Route::get('/produtos/categorias/{category}/editar', [ProductCategoryController::class, 'edit'])->name('categories.edit'); + Route::put('/produtos/categorias/{category}', [ProductCategoryController::class, 'update'])->name('categories.update'); + Route::delete('/produtos/categorias/{category}', [ProductCategoryController::class, 'destroy'])->name('categories.destroy'); + + Route::get('/produtos', [ProductController::class, 'index'])->name('products.index'); + Route::get('/produtos/criar', [ProductController::class, 'create'])->name('products.create'); + Route::post('/produtos', [ProductController::class, 'store'])->name('products.store'); + Route::get('/produtos/{product}/editar', [ProductController::class, 'edit'])->name('products.edit'); + Route::put('/produtos/{product}', [ProductController::class, 'update'])->name('products.update'); + Route::delete('/produtos/{product}', [ProductController::class, 'destroy'])->name('products.destroy'); + }); +}); diff --git a/app/Modules/Services/Http/Controllers/Admin/ServiceController.php b/app/Modules/Services/Http/Controllers/Admin/ServiceController.php new file mode 100755 index 0000000..f84eaa2 --- /dev/null +++ b/app/Modules/Services/Http/Controllers/Admin/ServiceController.php @@ -0,0 +1,85 @@ +paginate(10); + return view('services::admin.services.index', compact('services')); + } + + public function create() + { + return view('services::admin.services.create'); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:services', + 'description' => 'nullable|string', + 'icon' => 'nullable|string|max:100', + 'image' => 'nullable|image|max:2048', + 'order' => 'required|integer', + 'active' => 'required|boolean', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + $data['image'] = $request->file('image')->store('services', 'public'); + } + + Service::create($data); + + return redirect()->route('admin.services.index')->with('success', 'Serviço criado com sucesso.'); + } + + public function edit(Service $service) + { + return view('services::admin.services.edit', compact('service')); + } + + public function update(Request $request, Service $service) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'slug' => 'required|string|max:255|unique:services,slug,' . $service->id, + 'description' => 'nullable|string', + 'icon' => 'nullable|string|max:100', + 'image' => 'nullable|image|max:2048', + 'order' => 'required|integer', + 'active' => 'required|boolean', + ]); + + $data = $request->all(); + + if ($request->hasFile('image')) { + if ($service->image) { + Storage::disk('public')->delete($service->image); + } + $data['image'] = $request->file('image')->store('services', 'public'); + } + + $service->update($data); + + return redirect()->route('admin.services.index')->with('success', 'Serviço atualizado com sucesso.'); + } + + public function destroy(Service $service) + { + if ($service->image) { + Storage::disk('public')->delete($service->image); + } + $service->delete(); + return redirect()->route('admin.services.index')->with('success', 'Serviço excluído com sucesso.'); + } +} diff --git a/app/Modules/Services/Http/Controllers/ServicePageController.php b/app/Modules/Services/Http/Controllers/ServicePageController.php new file mode 100755 index 0000000..f38fc90 --- /dev/null +++ b/app/Modules/Services/Http/Controllers/ServicePageController.php @@ -0,0 +1,17 @@ +where('active', true)->firstOrFail(); + $services = Service::where('active', true)->orderBy('order')->get(); + return view('services::servicos', compact('page', 'services')); + } +} diff --git a/app/Modules/Services/Models/Service.php b/app/Modules/Services/Models/Service.php new file mode 100755 index 0000000..12d4c62 --- /dev/null +++ b/app/Modules/Services/Models/Service.php @@ -0,0 +1,10 @@ +loadViewsFrom( + __DIR__ . '/../resources/views', + 'services' + ); + + // Register module routes + Route::middleware(['web']) + ->group(__DIR__ . '/../routes/web.php'); + } +} diff --git a/app/Modules/Services/resources/views/admin/services/create.blade.php b/app/Modules/Services/resources/views/admin/services/create.blade.php new file mode 100755 index 0000000..38a54d3 --- /dev/null +++ b/app/Modules/Services/resources/views/admin/services/create.blade.php @@ -0,0 +1,110 @@ +@extends('layouts.admin') + +@section('title', 'Novo Serviço') + +@section('content') +
+

Novo Serviço

+ + Voltar + +
+ +
+
+
Informações do Serviço
+
+
+
+ @csrf + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + + Use Bootstrap Icons . + @error('icon')
{{ $message }}
@enderror +
+ +
+ + + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Services/resources/views/admin/services/edit.blade.php b/app/Modules/Services/resources/views/admin/services/edit.blade.php new file mode 100755 index 0000000..30ffae0 --- /dev/null +++ b/app/Modules/Services/resources/views/admin/services/edit.blade.php @@ -0,0 +1,117 @@ +@extends('layouts.admin') + +@section('title', 'Editar Serviço') + +@section('content') +
+

Editar Serviço: {{ $service->name }}

+ + Voltar + +
+ +
+
+
Informações do Serviço
+
+
+
+ @csrf + @method('PUT') + +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+
+
+
+ + + Use Bootstrap Icons . + @error('icon')
{{ $message }}
@enderror +
+ +
+ + @if($service->image) +
+ +
+ @endif + + Deixe em branco para manter a imagem atual. + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/app/Modules/Services/resources/views/admin/services/index.blade.php b/app/Modules/Services/resources/views/admin/services/index.blade.php new file mode 100755 index 0000000..053efec --- /dev/null +++ b/app/Modules/Services/resources/views/admin/services/index.blade.php @@ -0,0 +1,72 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Serviços') + +@section('content') +
+

Gerenciamento de Serviços

+ + Novo Serviço + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Serviços
+
+
+
+ + + + + + + + + + + + @foreach($services as $service) + + + + + + + + @endforeach + +
OrdemÍconeNomeStatusAções
{{ $service->order }} + @if($service->icon) + + @else + Sem ícone + @endif + {{ $service->name }} + + {{ $service->active ? 'Ativo' : 'Inativo' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $services->links() }} +
+
+
+@endsection diff --git a/app/Modules/Services/resources/views/servicos.blade.php b/app/Modules/Services/resources/views/servicos.blade.php new file mode 100755 index 0000000..951c65f --- /dev/null +++ b/app/Modules/Services/resources/views/servicos.blade.php @@ -0,0 +1,869 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') + + +
+
+
+
+
+ + Inovação e Tecnologia + +

+ Soluções Digitais de Alta Performance +

+

+ Desenvolvemos sistemas inteligentes, frameworks robustos e infraestrutura de ponta para impulsionar a transformação digital da sua empresa. +

+ +
+
+
+
+
+ +

Syspanel Suite

+

Excelência em cada detalhe

+
+
+
+
+
+
+
+ +
+
+
+

Por Que Escolher a Syspanel?

+

+ Somos especialistas em desenvolvimento de software e oferecemos soluções que transformam negócios através da tecnologia. +

+
+ +
+
+
+
+ +
+

Inovação Constante

+

Tecnologia de ponta em todos os projetos para garantir sua competitividade.

+
+
+
+
+
+ +
+

Segurança Robusta

+

Proteção avançada de dados e sistemas seguindo os mais altos padrões.

+
+
+
+
+
+ +
+

Suporte Especializado

+

Equipe técnica pronta para auxiliar sua empresa em qualquer desafio.

+
+
+
+
+
+ +
+

Qualidade Garantida

+

Entrega de projetos com excelência técnica e foco em resultados reais.

+
+
+
+
+
+
+
+ +
+
+

SysSiteCMS CMS

+ Gestão de Conteúdo +
+
+

+ Plataforma completa de CMS para gestão de sites institucionais, catálogos de produtos e marketing digital integrado. +

+
    +
  • Gestão de Produtos, Serviços e Galerias
  • +
  • Marketing: Banners, FAQ e Email Marketing
  • +
  • Central de Downloads e Suporte Técnico
  • +
  • Análise de Visitantes e SEO Avançado
  • +
+ +
+
+
+
+
+
+ +
+
+
+ + Gestão Empresarial + +

Sistemas ERP Inteligentes

+

Plataformas completas para integrar e otimizar todos os processos do seu negócio

+
+ +
+
+
+
+
+
+ +
+
+

SysERP

+ ERP Completo +
+
+

+ Plataforma online desenvolvida para interligar e auxiliar na organização e gestão de diversas áreas da empresa, possibilitando integrações e controles nos processos gerenciais. +

+
Funcionalidades Principais:
+
+
+
    +
  • Gestão Financeira
  • +
  • Controle de Estoque
  • +
  • Gestão de Vendas
  • +
+
+
+
    +
  • Produção e Serviços
  • +
  • Relatórios Avançados
  • +
  • Personalização
  • +
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Fluxo de Caixa Pro

+ Gestão Financeira Avançada +
+
+

+ Especializado em controle financeiro rigoroso, oferecendo ferramentas para gestão de fluxo de caixa, DRE gerencial e previsibilidade total para sua empresa. +

+
Recursos Avançados:
+
+
+
    +
  • DRE Automático
  • +
  • Conciliação OFX
  • +
  • Gráficos em Tempo Real
  • +
+
+
+
    +
  • Previsão de Saldo
  • +
  • Gestão de Contas
  • +
  • Multi-Empresa
  • +
+
+
+ +
+
+
+ +
+
+
+

Por que escolher o SysERP?

+
+
+ +
+
+
Integração em Tempo Real
+

Todos os setores da empresa conectados e sincronizados instantaneamente, eliminando falhas de comunicação.

+
+
+
+
+ +
+
+
Personalização Total
+

Adaptamos o sistema às necessidades específicas do seu negócio, criando módulos exclusivos se necessário.

+
+
+
+
+ +
+
+
Relatórios Inteligentes
+

Dashboard completo com métricas e indicadores de performance para tomadas de decisão rápidas.

+
+
+
+
+
+
+
+
+ +
+
+
+ + Desenvolvimento Web + +

Frameworks PHP Profissionais

+

Ferramentas poderosas para desenvolvimento de aplicações web modernas e escaláveis

+
+ +
+
+
+
+
+ +
+

SysFramework

+

+ Framework PHP moderno com arquitetura MVC modular, segurança robusta e ferramenta CLI potente. +

+
    +
  • Proteção CSRF, XSS
  • +
  • MVC Modular
  • +
  • Acesso Robusto
  • +
  • SysCli Integrado
  • +
+ + Explorar + +
+
+
+ +
+
+
+
+ +
+

SysMVC

+

+ Framework PHP poderoso e flexível para desenvolvimento de aplicações web com arquitetura MVC clássica. +

+
    +
  • Arquitetura MVC
  • +
  • Flexibilidade Total
  • +
  • Projetos Complexos
  • +
  • Desenvolvimento Ágil
  • +
+ + Explorar + +
+
+
+ +
+
+
+
+ +
+

SysVector

+

+ Framework PHP moderno com Bootstrap 5.3, Alpine.js e templates Twig para desenvolvimento ultra rápido. +

+
    +
  • Bootstrap 5.3
  • +
  • Alpine.js
  • +
  • Templates Twig
  • +
  • Modernidade
  • +
+ + Explorar + +
+
+
+
+
+
+ +
+
+
+ + Hospedagem e CMS + +

Infraestrutura e Gestão

+

Soluções completas de hospedagem profissional e sistemas de gerenciamento de conteúdo

+
+ +
+
+
+
+
+
+ +
+
+

SysHost

+ Hospedagem Profissional +
+
+

+ Hospedagem profissional com planos personalizados para sites, aplicativos, Laravel, WordPress e projetos SysMVC. Infraestrutura de alto desempenho. +

+
    +
  • Infraestrutura de alto desempenho
  • +
  • Suporte especializado 24/7
  • +
  • Planos flexíveis e personalizados
  • +
  • Backup automático
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

SysCMS

+ Gestão de Conteúdo +
+
+

+ Sistema de gerenciamento de conteúdo potente com suporte multi-idioma, SEO avançado e controle de acesso granular para sites modernos. +

+
    +
  • Arquitetura modular expansível
  • +
  • Suporte nativo a 5 idiomas
  • +
  • Otimização SEO avançada
  • +
  • Controle de acesso granular
  • +
+ +
+
+
+
+
+
+ +
+
+
+ + Serviços Adicionais + +

Soluções Complementares

+

Serviços especializados para complementar sua infraestrutura digital

+
+ +
+
+
+
+
+
+ +
+
+

Registro de Domínios

+ Identidade Digital +
+
+

+ Registramos seu domínio nacional ou internacional para proteger sua identidade digital e garantir presença online profissional para sua empresa. +

+
    +
  • Registro nacional e internacional
  • +
  • Gestão de DNS
  • +
  • Gestão de renovações
  • +
  • Proteção de identidade
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Registro de Marcas

+ INPI +
+
+

+ Proteção legal da sua marca junto ao INPI. Garantimos a exclusividade de uso do seu nome e logotipo em todo o território nacional. +

+
    +
  • Pesquisa de viabilidade
  • +
  • Protocolo de registro
  • +
  • Acompanhamento processual
  • +
  • Defesa e oposições
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Emails Corporativos

+ Comunicação +
+
+

+ Hospedagem de emails com seu domínio próprio. Infraestrutura robusta com proteção avançada e alta disponibilidade. +

+
    +
  • Emails personalizados
  • +
  • Proteção antivírus e antispam
  • +
  • Webmail moderno
  • +
  • Sincronização mobile
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Desenvolvimento Web

+ Web Design +
+
+

+ Criamos sites modernos, responsivos e otimizados para buscadores (SEO). Do site institucional ao e-commerce completo. +

+
    +
  • Sites responsivos
  • +
  • Otimização SEO
  • +
  • E-commerce integrado
  • +
  • Foco em conversão
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Softwares Sob Medida

+ Sistemas +
+
+

+ Desenvolvemos softwares personalizados para atender necessidades específicas. Sistemas desktop, web e mobile. +

+
    +
  • Sistemas personalizados
  • +
  • Aplicativos mobile
  • +
  • Integração de APIs
  • +
  • Suporte contínuo
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

ChatBots com IA

+ Inteligência Artificial +
+
+

+ Implementamos assistentes virtuais inteligentes para atendimento automático de clientes. ChatBots que respondem sobre sua empresa, produtos e serviços 24 horas por dia. +

+
    +
  • Atendimento 24/7 automático
  • +
  • Base de conhecimento personalizada
  • +
  • Integração com Inteligência Artificial
  • +
  • Coleta de contatos e avaliações
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Servidores Linux

+ Local / Nuvem +
+
+

+ Implantação e configuração de servidores Linux em infraestrutura local ou nuvem. Alta disponibilidade, segurança e desempenho otimizado. +

+
    +
  • Implantação local e nuvem
  • +
  • Configuração e hardening
  • +
  • Monitoramento 24/7
  • +
  • Alta disponibilidade
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

Assistentes Digitais com IA

+ Inteligência Artificial +
+
+

+ Implantação de assistentes digitais inteligentes com inteligência artificial para automatizar atendimento, suporte e processos internos da sua empresa. +

+
    +
  • Chatbots inteligentes
  • +
  • Automação de atendimento
  • +
  • Integração com plataformas
  • +
  • Personalização por negócio
  • +
+ +
+
+
+ +
+
+
+
+
+ +
+
+

SysSiteCMS CMS

+ Gestão de Conteúdo +
+
+

+ Plataforma completa de CMS para gestão de sites institucionais, catálogos de produtos e marketing digital integrado. +

+
    +
  • Gestão de Produtos, Serviços e Galerias
  • +
  • Marketing: Banners, FAQ e Email Marketing
  • +
  • Central de Downloads e Suporte Técnico
  • +
  • Análise de Visitantes e SEO Avançado
  • +
+ +
+
+
+
+
+
+ +
+
+
+
+

Precisa de Uma Solução Personalizada?

+

+ Desenvolvemos sistemas sob medida para atender necessidades específicas do seu negócio. Nossa equipe tem experiência em projetos complexos e inovadores. +

+
    +
  • +
    + +
    + Análise completa dos requisitos +
  • +
  • +
    + +
    + Desenvolvimento ágil e iterativo +
  • +
  • +
    + +
    + Suporte contínuo e atualizações +
  • +
  • +
    + +
    + Treinamento completo da equipe +
  • +
+
+
+
+
+
+
+ +
+

Solicite um Orçamento

+

Entre em contato e descubra como podemos transformar seu negócio através da tecnologia.

+
+ +
+
+
+
+
+
+@endsection diff --git a/app/Modules/Services/routes/web.php b/app/Modules/Services/routes/web.php new file mode 100755 index 0000000..d4caafd --- /dev/null +++ b/app/Modules/Services/routes/web.php @@ -0,0 +1,31 @@ +name('servicos'); + +/* +|-------------------------------------------------------------------------- +| Services Module - Admin Routes +|-------------------------------------------------------------------------- +*/ + +Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { + // Services management (permission: services) + Route::middleware('permission:services')->group(function () { + Route::get('/servicos', [ServiceController::class, 'index'])->name('services.index'); + Route::get('/servicos/criar', [ServiceController::class, 'create'])->name('services.create'); + Route::post('/servicos', [ServiceController::class, 'store'])->name('services.store'); + Route::get('/servicos/{service}/editar', [ServiceController::class, 'edit'])->name('services.edit'); + Route::put('/servicos/{service}', [ServiceController::class, 'update'])->name('services.update'); + Route::delete('/servicos/{service}', [ServiceController::class, 'destroy'])->name('services.destroy'); + }); +}); diff --git a/app/Notifications/FeatureRequestNotification.php b/app/Notifications/FeatureRequestNotification.php new file mode 100755 index 0000000..4828ed3 --- /dev/null +++ b/app/Notifications/FeatureRequestNotification.php @@ -0,0 +1,54 @@ +user = $user; + $this->description = $description; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject('Solicitação de Nova Funcionalidade - ' . config('app.name')) + ->greeting('Olá, equipe SysSiteCMS!') + ->line('Um usuário solicitou uma nova funcionalidade para o sistema.') + ->line('**Dados do Usuário:**') + ->line('- **Nome:** ' . $this->user->name) + ->line('- **E-mail:** ' . $this->user->email) + ->line('- **Empresa:** ' . ($this->user->company ?? 'Não informado')) + ->line('**Necessidade Descrita:**') + ->line($this->description) + ->line('Favor entrar em contato com o cliente para orçamento.'); + } +} diff --git a/app/Notifications/GalleryActionNotification.php b/app/Notifications/GalleryActionNotification.php new file mode 100755 index 0000000..4b1e070 --- /dev/null +++ b/app/Notifications/GalleryActionNotification.php @@ -0,0 +1,100 @@ +gallery = $gallery; + $this->user = $user; + $this->action = $action; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + $prefs = $notifiable->preferences ?? []; + + if ($prefs['notifications_gallery_actions'] ?? true) { + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + $actionMap = [ + 'created' => 'criada', + 'updated' => 'atualizada', + 'photo_added' => 'com nova foto adicionada', + 'photo_deleted' => 'com foto removida', + ]; + + $actionText = $actionMap[$this->action] ?? 'modificada'; + + return (new MailMessage) + ->subject('Galeria ' . ucfirst($actionText) . ' - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Uma galeria de fotos foi ' . $actionText . ' por ' . $this->user->name . '.') + ->line('**Título da Galeria:** ' . $this->gallery->title) + ->action('Gerenciar Galerias', route('admin.galleries.index')) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + $actionMap = [ + 'created' => 'criada', + 'updated' => 'atualizada', + 'photo_added' => 'foto adicionada', + 'photo_deleted' => 'foto removida', + ]; + + $actionText = $actionMap[$this->action] ?? 'modificada'; + + return [ + 'gallery_id' => $this->gallery->id, + 'name' => $this->user->name, + 'email' => $this->user->email, + 'action' => $this->action, + 'message' => "Galeria '{$this->gallery->title}': {$actionText}.", + 'link' => route('admin.galleries.edit', $this->gallery->id) + ]; + } +} diff --git a/app/Notifications/NewContactMessageNotification.php b/app/Notifications/NewContactMessageNotification.php new file mode 100755 index 0000000..b22bfd9 --- /dev/null +++ b/app/Notifications/NewContactMessageNotification.php @@ -0,0 +1,80 @@ +contact = $contact; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + + // Verifica as preferências do usuário (notifiable) + $prefs = $notifiable->preferences ?? []; + + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject('Novo Contato Recebido - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Você recebeu uma nova mensagem de contato através do site.') + ->line('**Nome:** ' . $this->contact->name) + ->line('**E-mail:** ' . $this->contact->email) + ->line('**Mensagem:**') + ->line($this->contact->message) + ->action('Visualizar no Painel', route('admin.contacts.show', $this->contact->id)) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + 'contact_id' => $this->contact->id, + 'name' => $this->contact->name, + 'email' => $this->contact->email, + 'message' => 'Nova mensagem de contato recebida.', + 'link' => route('admin.contacts.show', $this->contact->id) + ]; + } +} diff --git a/app/Notifications/NewUserRegisteredNotification.php b/app/Notifications/NewUserRegisteredNotification.php new file mode 100755 index 0000000..8d52db7 --- /dev/null +++ b/app/Notifications/NewUserRegisteredNotification.php @@ -0,0 +1,79 @@ +newUser = $newUser; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + $prefs = $notifiable->preferences ?? []; + + // Se o admin quiser receber notificações de novos usuários + if ($prefs['notifications_new_users'] ?? true) { + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject('Novo Usuário Cadastrado - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Um novo usuário acaba de se cadastrar no site.') + ->line('**Nome:** ' . $this->newUser->name) + ->line('**E-mail:** ' . $this->newUser->email) + ->line('**Empresa:** ' . ($this->newUser->company ?? 'Não informado')) + ->action('Gerenciar Usuários', route('admin.users.index')) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + 'user_id' => $this->newUser->id, + 'name' => $this->newUser->name, + 'email' => $this->newUser->email, + 'message' => 'Novo usuário cadastrado: ' . $this->newUser->name, + 'link' => route('admin.users.edit', $this->newUser->id) + ]; + } +} diff --git a/app/Notifications/NewsActionNotification.php b/app/Notifications/NewsActionNotification.php new file mode 100755 index 0000000..5b15ce1 --- /dev/null +++ b/app/Notifications/NewsActionNotification.php @@ -0,0 +1,98 @@ +news = $news; + $this->user = $user; + $this->action = $action; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + $prefs = $notifiable->preferences ?? []; + + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + $actionMap = [ + 'created' => 'criada', + 'updated' => 'atualizada', + 'photo_added' => 'com nova imagem adicionada', + 'photo_deleted' => 'com imagem removida', + ]; + + $actionText = $actionMap[$this->action] ?? 'modificada'; + + return (new MailMessage) + ->subject('Notícia ' . ucfirst($actionText) . ' - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Uma notícia foi ' . $actionText . ' por ' . $this->user->name . '.') + ->line('**Título:** ' . $this->news->title) + ->action('Gerenciar Notícias', route('admin.news.index')) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + $actionMap = [ + 'created' => 'criada', + 'updated' => 'atualizada', + 'photo_added' => 'imagem adicionada', + 'photo_deleted' => 'imagem removida', + ]; + + $actionText = $actionMap[$this->action] ?? 'modificada'; + + return [ + 'news_id' => $this->news->id, + 'name' => $this->user->name, + 'email' => $this->user->email, + 'action' => $this->action, + 'message' => "Notícia '{$this->news->title}': {$actionText}.", + 'link' => route('admin.news.edit', $this->news->id) + ]; + } +} diff --git a/app/Notifications/PageActionNotification.php b/app/Notifications/PageActionNotification.php new file mode 100755 index 0000000..21fb9d1 --- /dev/null +++ b/app/Notifications/PageActionNotification.php @@ -0,0 +1,88 @@ +page = $page; + $this->user = $user; + $this->action = $action; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + $channels = []; + $prefs = $notifiable->preferences ?? []; + + // Se o admin quiser receber notificações de alterações em páginas + if ($prefs['notifications_page_actions'] ?? true) { + if ($prefs['notifications_email'] ?? true) { + $channels[] = 'mail'; + } + if ($prefs['notifications_system'] ?? true) { + $channels[] = 'database'; + } + } + + return $channels; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + $actionText = $this->action === 'created' ? 'criada' : 'atualizada'; + + return (new MailMessage) + ->subject('Página ' . ucfirst($actionText) . ' - ' . config('app.name')) + ->greeting('Olá, ' . $notifiable->name) + ->line('Uma página do site foi ' . $actionText . ' por ' . $this->user->name . '.') + ->line('**Título da Página:** ' . $this->page->title) + ->line('**Slug:** ' . $this->page->slug) + ->action('Gerenciar Páginas', route('admin.pages.index')) + ->line('Obrigado por usar nosso sistema!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + $actionText = $this->action === 'created' ? 'criada' : 'atualizada'; + + return [ + 'page_id' => $this->page->id, + 'name' => $this->user->name, + 'email' => $this->user->email, + 'action' => $this->action, + 'message' => "Página '{$this->page->title}' foi {$actionText}.", + 'link' => route('admin.pages.edit', $this->page->id) + ]; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100755 index 0000000..452e6b6 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,24 @@ + \App\Modules\Products\Providers\ProductsServiceProvider::class, + 'services' => \App\Modules\Services\Providers\ServicesServiceProvider::class, + 'chatbot' => \App\Modules\Chatbot\Providers\ChatbotServiceProvider::class, + ]; + + /** + * Register services. + */ + public function register(): void + { + // Register the ModuleManager as a singleton + $this->app->singleton(ModuleManager::class, function ($app) { + return new ModuleManager(); + }); + + // Register enabled module service providers + $manager = $this->app->make(ModuleManager::class); + + foreach ($this->moduleProviders as $module => $providerClass) { + if ($manager->isEnabled($module) && class_exists($providerClass)) { + $this->app->register($providerClass); + } + } + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // Module bootstrapping is handled by individual module providers + } +} diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php new file mode 100755 index 0000000..bd6a13c --- /dev/null +++ b/app/Providers/ViewServiceProvider.php @@ -0,0 +1,110 @@ +pluck('slug')->toArray(); + + // Filter out module-specific pages when modules are disabled + if (!module_enabled('products')) { + $activePagesSlugs = array_diff($activePagesSlugs, ['produtos']); + } + if (!module_enabled('services')) { + $activePagesSlugs = array_diff($activePagesSlugs, ['servicos']); + } + + $customPages = Page::where('active', true) + ->whereNotIn('slug', $coreSlugs) + ->get(['title', 'slug']); + + $activeGalleries = \App\Models\Gallery::where('active', true)->get(['title', 'slug', 'category_id']); + $galleryCategories = []; + $activeGalleryCategories = \App\Models\GalleryCategory::where('active', true)->orderBy('name')->get(); + foreach ($activeGalleryCategories as $category) { + $galleries = \App\Models\Gallery::where('active', true) + ->where('category_id', $category->id) + ->orderBy('title') + ->get(['title', 'slug']); + if ($galleries->count() > 0) { + $galleryCategories[$category->name] = $galleries; + } + } + + // Global Company Data + $company = [ + 'name' => \App\Models\Setting::get('company_name', 'Syspanel'), + 'cnpj' => \App\Models\Setting::get('company_cnpj'), + 'email' => \App\Models\Setting::get('company_email'), + 'phone' => \App\Models\Setting::get('company_phone', '(11) 99999-9999'), + 'address' => \App\Models\Setting::get('company_address', 'Endereço não configurado'), + 'hours' => \App\Models\Setting::get('company_hours'), + 'instagram' => \App\Models\Setting::get('company_instagram'), + 'facebook' => \App\Models\Setting::get('company_facebook'), + 'linkedin' => \App\Models\Setting::get('company_linkedin'), + ]; + + $view->with([ + 'activePages' => $activePagesSlugs, + 'customPages' => $customPages, + 'activeGalleries' => $activeGalleries, + 'galleryCategories' => $galleryCategories, + 'activeGalleryCategories' => $activeGalleryCategories, + 'company' => (object)$company, + 'whatsapp_enabled' => $this->checkWhatsappAvailability() + ]); + }); + } + + /** + * Check if the WhatsApp button should be visible based on schedule. + */ + private function checkWhatsappAvailability(): bool + { + if (\App\Models\Setting::get('whatsapp_floating_button') !== 'on') { + return false; + } + + $now = now(); + $currentTime = $now->format('H:i'); + + $startTime = \App\Models\Setting::get('whatsapp_start_time', '08:00'); + $endTime = \App\Models\Setting::get('whatsapp_end_time', '18:00'); + $lunchStart = \App\Models\Setting::get('whatsapp_lunch_start'); + $lunchEnd = \App\Models\Setting::get('whatsapp_lunch_end'); + + // Outside business hours + if ($currentTime < $startTime || $currentTime > $endTime) { + return false; + } + + // If lunch break is configured + if ($lunchStart && $lunchEnd) { + if ($currentTime >= $lunchStart && $currentTime <= $lunchEnd) { + return false; + } + } + + return true; + } +} diff --git a/app/Support/ModuleManager.php b/app/Support/ModuleManager.php new file mode 100755 index 0000000..9fafb07 --- /dev/null +++ b/app/Support/ModuleManager.php @@ -0,0 +1,69 @@ + + */ + public function getEnabledModuleNames(): array + { + return array_keys($this->getEnabledModules()); + } +} diff --git a/app/Support/helpers.php b/app/Support/helpers.php new file mode 100755 index 0000000..20cc810 --- /dev/null +++ b/app/Support/helpers.php @@ -0,0 +1,29 @@ +isEnabled($module); + } +} + +if (!function_exists('module_disabled')) { + /** + * Check if a module is disabled. + * + * @param string $module The module identifier + * @return bool + */ + function module_disabled(string $module): bool + { + return !module_enabled($module); + } +} diff --git a/app/obrigatory.md b/app/obrigatory.md new file mode 100755 index 0000000..0d2b20f --- /dev/null +++ b/app/obrigatory.md @@ -0,0 +1,22 @@ +# Regras Obrigatórias + +## Diretrizes Principais + +- Responda em português do Brasil +- Não modifique arquivos fora do escopo +- Não altere arquitetura sem autorização +- Não refatore código não solicitado +- Gere apenas o código solicitado +- Se houver dúvidas, pergunte antes +- Sempre salve os arquivos criados e/ou editados +- Exceção: explique brevemente se houver risco de segurança, bug ou escolha arquitetural importante +- Evite markdown no chat, use apenas quando essencial +- README.md, comentários nos scripts e códigos e LICENSE: sempre em Inglês +- Todos os formulários públicos devem ter captcha +- Todos os formulários devem utilizar CSRF +- Proibido fazer alterações fora do container e/ou diretório do projeto +- Proibido fazer alterações fora da pasta do projeto +- Proibido apagar containers, imagens docker, volumes docker +- Proibido apagar arquivos fora da pasta do projeto +- Proibido apagar banco de dados +- Modo: conservador \ No newline at end of file diff --git a/artisan b/artisan new file mode 100755 index 0000000..c35e31d --- /dev/null +++ b/artisan @@ -0,0 +1,18 @@ +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100755 index 0000000..4309860 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,28 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->alias([ + 'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class, + 'permission' => \App\Http\Middleware\CheckPermission::class, + 'throttle.contact' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + ]); + + $middleware->web(append: [ + \App\Http\Middleware\CheckMaintenanceMode::class, + \App\Http\Middleware\TrackVisitor::class, + ]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100755 index 0000000..841ce50 --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,7 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-03-10T16:41:02+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:27:06+00:00" + }, + { + "name": "laravel/framework", + "version": "v13.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "9e48d1fe933e89de628dafa167d2c5778566d4cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/9e48d1fe933e89de628dafa167d2c5778566d4cf", + "reference": "9e48d1fe933e89de628dafa167d2c5778566d4cf", + "shasum": "" + }, + "require": { + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^2.0.10", + "league/commonmark": "^2.8.1", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.1", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.4.0 || ^8.0.0", + "symfony/error-handler": "^7.4.0 || ^8.0.0", + "symfony/finder": "^7.4.0 || ^8.0.0", + "symfony/http-foundation": "^7.4.0 || ^8.0.0", + "symfony/http-kernel": "^7.4.0 || ^8.0.0", + "symfony/mailer": "^7.4.0 || ^8.0.0", + "symfony/mime": "^7.4.0 || ^8.0.0", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.4.5 || ^8.0.5", + "symfony/routing": "^7.4.0 || ^8.0.0", + "symfony/uid": "^7.4.0 || ^8.0.0", + "symfony/var-dumper": "^7.4.0 || ^8.0.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1 || 2.0", + "psr/log-implementation": "1.0 || 2.0 || 3.0", + "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/json-schema": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^11.0.0", + "pda/pheanstalk": "^7.0.0 || ^8.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3", + "predis/predis": "^2.3 || ^3.0", + "resend/resend-php": "^1.0", + "symfony/cache": "^7.4.0 || ^8.0.0", + "symfony/http-client": "^7.4.0 || ^8.0.0", + "symfony/psr-http-message-bridge": "^7.4.0 || ^8.0.0", + "symfony/translation": "^7.4.0 || ^8.0.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0 || ^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^7.0 || ^8.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).", + "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.4 || ^8.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.4 || ^8.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.4 || ^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "13.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-24T18:42:09+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.16", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.16" + }, + "time": "2026-03-23T14:35:33+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-02-20T19:59:49+00:00" + }, + { + "name": "laravel/tinker", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "cc74081282ba2e3dae1f0068ccb330370d24634e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/cc74081282ba2e3dae1f0068ccb330370d24634e", + "reference": "cc74081282ba2e3dae1f0068ccb330370d24634e", + "shasum": "" + }, + "require": { + "illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "php": "^8.1", + "psy/psysh": "^0.12.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5|^11.5" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^8.0|^9.0|^10.0|^11.0|^12.0|^13.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v3.0.0" + }, + "time": "2026-03-17T14:53:17+00:00" + }, + { + "name": "laravel/ui", + "version": "v4.6.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/ui.git", + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/ui/zipball/ff27db15416c1ed8ad9848f5692e47595dd5de27", + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/validation": "^9.21|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.3|^10.4|^11.5|^12.5|^13.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "support": { + "source": "https://github.com/laravel/ui/tree/v4.6.3" + }, + "time": "2026-03-17T13:41:52+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-03-19T13:16:38+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.33.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "570b8871e0ce693764434b29154c54b434905350" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + }, + "time": "2026-03-25T07:59:30+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.11.3", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2026-03-11T17:23:39+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.3" + }, + "time": "2026-02-13T03:05:33+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "It's like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.22", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.22" + }, + "time": "2026-03-22T23:03:24+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-12T15:39:26+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T14:06:20+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T07:53:42+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-20T16:42:42+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T11:45:34+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-29T09:40:50+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:15:18+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T16:33:18+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-25T16:50:00+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-05T15:24:09+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "608476f4604102976d687c483ac63a79ba18cc97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T15:07:59+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-25T16:50:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T09:33:46+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T07:53:42+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-03T23:30:35+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-15T10:53:20+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + } + ], + "packages-dev": [ + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.24|^11.0|^12.0|^13.0", + "illuminate/log": "^10.24|^11.0|^12.0|^13.0", + "illuminate/process": "^10.24|^11.0|^12.0|^13.0", + "illuminate/support": "^10.24|^11.0|^12.0|^13.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0|^12.0|^13.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.13|^9.17|^10.8|^11.0", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", + "phpstan/phpstan": "^1.12.27", + "symfony/var-dumper": "^6.3|^7.0|^8.0", + "symfony/yaml": "^6.3|^7.0|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "dev", + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2026-02-09T13:44:54+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.94.2", + "illuminate/view": "^12.54.1", + "larastan/larastan": "^3.9.3", + "laravel-zero/framework": "^12.0.5", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.0" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "dev", + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2026-03-12T15:51:39+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.9.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", + "php": "^8.2.0", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "conflict": { + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2026-02-17T17:33:08+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.5.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-02-06T06:01:44+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T14:04:18+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.5.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.3", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.4", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:38:40+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2025-09-14T09:36:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a7de5df2e094f9a80b40a522391a7e6022df5f6", + "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:28:48+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-03-15T07:05:40+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:16:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:57:12+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-12-08T11:19:18+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.3" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config/app.php b/config/app.php new file mode 100755 index 0000000..28be821 --- /dev/null +++ b/config/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100755 index 0000000..d7568ff --- /dev/null +++ b/config/auth.php @@ -0,0 +1,117 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the number of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100755 index 0000000..c68acdf --- /dev/null +++ b/config/cache.php @@ -0,0 +1,130 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", + | "failover", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + 'failover' => [ + 'driver' => 'failover', + 'stores' => [ + 'database', + 'array', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), + + /* + |-------------------------------------------------------------------------- + | Serializable Classes + |-------------------------------------------------------------------------- + | + | This value determines the classes that can be unserialized from cache + | storage. By default, no PHP classes will be unserialized from your + | cache to prevent gadget chain attacks if your APP_KEY is leaked. + | + */ + + 'serializable_classes' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100755 index 0000000..64709ce --- /dev/null +++ b/config/database.php @@ -0,0 +1,184 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => env('DB_SSLMODE', 'prefer'), + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100755 index 0000000..37d8fca --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100755 index 0000000..b09cb25 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'handler_with' => [ + 'stream' => 'php://stderr', + ], + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100755 index 0000000..e32e88d --- /dev/null +++ b/config/mail.php @@ -0,0 +1,118 @@ + env('MAIL_MAILER', 'log'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "resend", "log", "array", + | "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + 'retry_after' => 60, + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')), + ], + +]; diff --git a/config/modules.php b/config/modules.php new file mode 100755 index 0000000..0dfa946 --- /dev/null +++ b/config/modules.php @@ -0,0 +1,67 @@ + [ + 'enabled' => env('MODULE_PRODUCTS_ENABLED', true), + 'name' => 'Products', + 'description' => 'Product catalog management with categories', + ], + + /* + |---------------------------------------------------------------------- + | Services Module + |---------------------------------------------------------------------- + | + | Manages the services catalog and related admin features. + | Disable this module if the site does not need service listings. + | + */ + 'services' => [ + 'enabled' => env('MODULE_SERVICES_ENABLED', true), + 'name' => 'Services', + 'description' => 'Service catalog management', + ], + + /* + |---------------------------------------------------------------------- + | Chatbot Module + |---------------------------------------------------------------------- + | + | AI-powered customer service chatbot using OpenRouter.ai API. + | Provides a floating chat widget for visitors and a knowledge base + | management interface for admins. Disable this module if the site + | does not need an AI chatbot. + | + */ + 'chatbot' => [ + 'enabled' => env('MODULE_CHATBOT_ENABLED', true), + 'name' => 'Chatbot', + 'description' => 'AI-powered customer service chatbot with OpenRouter', + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100755 index 0000000..79c2c0a --- /dev/null +++ b/config/queue.php @@ -0,0 +1,129 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", + | "deferred", "background", "failover", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + 'deferred' => [ + 'driver' => 'deferred', + ], + + 'background' => [ + 'driver' => 'background', + ], + + 'failover' => [ + 'driver' => 'failover', + 'connections' => [ + 'database', + 'deferred', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100755 index 0000000..6a90eb8 --- /dev/null +++ b/config/services.php @@ -0,0 +1,38 @@ + [ + 'key' => env('POSTMARK_API_KEY'), + ], + + 'resend' => [ + 'key' => env('RESEND_API_KEY'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100755 index 0000000..f574482 --- /dev/null +++ b/config/session.php @@ -0,0 +1,233 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug((string) env('APP_NAME', 'laravel')).'-session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain without subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + + /* + |-------------------------------------------------------------------------- + | Session Serialization + |-------------------------------------------------------------------------- + | + | This value controls the serialization strategy for session data, which + | is JSON by default. Setting this to "php" allows the storage of PHP + | objects in the session but can make an application vulnerable to + | "gadget chain" serialization attacks if the APP_KEY is leaked. + | + | Supported: "json", "php" + | + */ + + 'serialization' => 'json', + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100755 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100755 index 0000000..c4ceb07 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,45 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100755 index 0000000..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100755 index 0000000..06dc7a5 --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->bigInteger('expiration')->index(); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->bigInteger('expiration')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100755 index 0000000..425e705 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2026_03_30_213513_add_user_type_to_users_table.php b/database/migrations/2026_03_30_213513_add_user_type_to_users_table.php new file mode 100755 index 0000000..9885aea --- /dev/null +++ b/database/migrations/2026_03_30_213513_add_user_type_to_users_table.php @@ -0,0 +1,28 @@ +enum('user_type', ['Cliente', 'Fornecedor', 'Colaborador', 'Outro'])->after('email')->default('Cliente'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('user_type'); + }); + } +}; diff --git a/database/migrations/2026_03_30_213636_add_additional_fields_to_users_table.php b/database/migrations/2026_03_30_213636_add_additional_fields_to_users_table.php new file mode 100755 index 0000000..91a4016 --- /dev/null +++ b/database/migrations/2026_03_30_213636_add_additional_fields_to_users_table.php @@ -0,0 +1,30 @@ +string('company')->nullable()->after('user_type'); + $table->string('website')->nullable()->after('company'); + $table->string('whatsapp')->nullable()->after('website'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['company', 'website', 'whatsapp']); + }); + } +}; diff --git a/database/migrations/2026_03_30_213931_create_contact_subjects_table.php b/database/migrations/2026_03_30_213931_create_contact_subjects_table.php new file mode 100755 index 0000000..d44cbf0 --- /dev/null +++ b/database/migrations/2026_03_30_213931_create_contact_subjects_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name'); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contact_subjects'); + } +}; diff --git a/database/migrations/2026_03_30_213932_create_contact_messages_table.php b/database/migrations/2026_03_30_213932_create_contact_messages_table.php new file mode 100755 index 0000000..4e45312 --- /dev/null +++ b/database/migrations/2026_03_30_213932_create_contact_messages_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('name'); + $table->string('email'); + $table->string('company')->nullable(); + $table->string('website')->nullable(); + $table->string('whatsapp')->nullable(); + $table->foreignId('subject_id')->constrained('contact_subjects')->onDelete('cascade'); + $table->text('message'); + $table->boolean('read')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contact_messages'); + } +}; diff --git a/database/migrations/2026_03_30_214111_update_user_types_in_users_table.php b/database/migrations/2026_03_30_214111_update_user_types_in_users_table.php new file mode 100755 index 0000000..351ef26 --- /dev/null +++ b/database/migrations/2026_03_30_214111_update_user_types_in_users_table.php @@ -0,0 +1,39 @@ +string('user_type')->change(); + }); + + // 2. Atualiza os valores antigos para o novo padrão + DB::table('users')->whereIn('user_type', ['Cliente', 'Fornecedor', 'Outro'])->update(['user_type' => 'User']); + // Se houver algum 'Colaborador', ele permanece como está. + + // 3. Define o novo enum + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Admin', 'Colaborador', 'User'])->default('User')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Cliente', 'Fornecedor', 'Colaborador', 'Outro'])->default('Cliente')->change(); + }); + } +}; diff --git a/database/migrations/2026_03_30_214755_update_user_types_v2_in_users_table.php b/database/migrations/2026_03_30_214755_update_user_types_v2_in_users_table.php new file mode 100755 index 0000000..ba4aafa --- /dev/null +++ b/database/migrations/2026_03_30_214755_update_user_types_v2_in_users_table.php @@ -0,0 +1,38 @@ +string('user_type')->change(); + }); + + // 2. Atualiza valores antigos se necessário (Colaborador -> Member) + DB::table('users')->where('user_type', 'Colaborador')->update(['user_type' => 'Member']); + + // 3. Define o novo enum com os 5 tipos solicitados + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Admin', 'Member', 'Cliente', 'Fornecedor', 'User'])->default('User')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Admin', 'Colaborador', 'User'])->default('User')->change(); + }); + } +}; diff --git a/database/migrations/2026_03_30_220230_create_contact_responses_table.php b/database/migrations/2026_03_30_220230_create_contact_responses_table.php new file mode 100755 index 0000000..0730ae4 --- /dev/null +++ b/database/migrations/2026_03_30_220230_create_contact_responses_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('contact_message_id')->constrained()->onDelete('cascade'); + $table->foreignId('user_id')->constrained(); // Quem respondeu + $table->text('message'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contact_responses'); + } +}; diff --git a/database/migrations/2026_03_30_221133_create_pages_table.php b/database/migrations/2026_03_30_221133_create_pages_table.php new file mode 100755 index 0000000..01f4b19 --- /dev/null +++ b/database/migrations/2026_03_30_221133_create_pages_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('slug')->unique(); + $table->string('title'); + $table->longText('content')->nullable(); + $table->string('meta_description')->nullable(); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pages'); + } +}; diff --git a/database/migrations/2026_03_30_222351_add_seo_fields_to_pages_table.php b/database/migrations/2026_03_30_222351_add_seo_fields_to_pages_table.php new file mode 100755 index 0000000..fe16814 --- /dev/null +++ b/database/migrations/2026_03_30_222351_add_seo_fields_to_pages_table.php @@ -0,0 +1,32 @@ +string('meta_keywords')->nullable()->after('meta_description'); + $table->string('og_title')->nullable()->after('meta_keywords'); + $table->string('og_description')->nullable()->after('og_title'); + $table->string('og_image')->nullable()->after('og_description'); + $table->string('canonical_url')->nullable()->after('og_image'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn(['meta_keywords', 'og_title', 'og_description', 'og_image', 'canonical_url']); + }); + } +}; diff --git a/database/migrations/2026_03_30_222351_create_settings_table.php b/database/migrations/2026_03_30_222351_create_settings_table.php new file mode 100755 index 0000000..025b1b7 --- /dev/null +++ b/database/migrations/2026_03_30_222351_create_settings_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('key')->unique(); + $table->text('value')->nullable(); + $table->string('group')->default('general'); // general, seo, scripts + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('settings'); + } +}; diff --git a/database/migrations/2026_03_30_223404_create_faqs_table.php b/database/migrations/2026_03_30_223404_create_faqs_table.php new file mode 100755 index 0000000..b7caf07 --- /dev/null +++ b/database/migrations/2026_03_30_223404_create_faqs_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('question'); + $table->text('answer'); + $table->integer('order')->default(0); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('faqs'); + } +}; diff --git a/database/migrations/2026_03_30_223924_create_downloads_table.php b/database/migrations/2026_03_30_223924_create_downloads_table.php new file mode 100755 index 0000000..f109c1a --- /dev/null +++ b/database/migrations/2026_03_30_223924_create_downloads_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('title'); + $table->text('description')->nullable(); + $table->string('file_path'); + $table->string('file_type')->nullable(); // Ex: Driver, Manual, Software + $table->bigInteger('download_count')->default(0); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('downloads'); + } +}; diff --git a/database/migrations/2026_03_30_225430_create_galleries_table.php b/database/migrations/2026_03_30_225430_create_galleries_table.php new file mode 100755 index 0000000..f17b41a --- /dev/null +++ b/database/migrations/2026_03_30_225430_create_galleries_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('galleries'); + } +}; diff --git a/database/migrations/2026_03_30_225431_create_gallery_images_table.php b/database/migrations/2026_03_30_225431_create_gallery_images_table.php new file mode 100755 index 0000000..c001053 --- /dev/null +++ b/database/migrations/2026_03_30_225431_create_gallery_images_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('gallery_id')->constrained()->onDelete('cascade'); + $table->string('file_path'); + $table->string('description')->nullable(); + $table->integer('order')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gallery_images'); + } +}; diff --git a/database/migrations/2026_03_30_230926_add_avatar_to_users_table.php b/database/migrations/2026_03_30_230926_add_avatar_to_users_table.php new file mode 100755 index 0000000..098441a --- /dev/null +++ b/database/migrations/2026_03_30_230926_add_avatar_to_users_table.php @@ -0,0 +1,32 @@ +string('avatar')->nullable()->after('email'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + if (Schema::hasColumn('users', 'avatar')) { + $table->dropColumn('avatar'); + } + }); + } +}; diff --git a/database/migrations/2026_03_30_232129_add_preferences_to_users_table.php b/database/migrations/2026_03_30_232129_add_preferences_to_users_table.php new file mode 100755 index 0000000..6390a65 --- /dev/null +++ b/database/migrations/2026_03_30_232129_add_preferences_to_users_table.php @@ -0,0 +1,32 @@ +json('preferences')->nullable()->after('whatsapp'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + if (Schema::hasColumn('users', 'preferences')) { + $table->dropColumn('preferences'); + } + }); + } +}; diff --git a/database/migrations/2026_03_30_232412_create_notifications_table.php b/database/migrations/2026_03_30_232412_create_notifications_table.php new file mode 100755 index 0000000..d738032 --- /dev/null +++ b/database/migrations/2026_03_30_232412_create_notifications_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; diff --git a/database/migrations/2026_03_31_000434_create_products_table.php b/database/migrations/2026_03_31_000434_create_products_table.php new file mode 100755 index 0000000..cbe71bc --- /dev/null +++ b/database/migrations/2026_03_31_000434_create_products_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->decimal('price', 10, 2)->nullable(); + $table->string('image')->nullable(); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('products'); + } +}; diff --git a/database/migrations/2026_03_31_000500_create_product_categories_table.php b/database/migrations/2026_03_31_000500_create_product_categories_table.php new file mode 100755 index 0000000..063e7fc --- /dev/null +++ b/database/migrations/2026_03_31_000500_create_product_categories_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->boolean('active')->default(true); + $table->integer('order')->default(0); + $table->timestamps(); + }); + + Schema::table('products', function (Blueprint $table) { + $table->foreignId('category_id')->nullable()->after('id')->constrained('product_categories')->onDelete('set null'); + }); + } + + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + $table->dropForeign(['category_id']); + $table->dropColumn('category_id'); + }); + Schema::dropIfExists('product_categories'); + } +}; diff --git a/database/migrations/2026_03_31_000501_add_fields_to_product_categories_table.php b/database/migrations/2026_03_31_000501_add_fields_to_product_categories_table.php new file mode 100755 index 0000000..9ffaf6c --- /dev/null +++ b/database/migrations/2026_03_31_000501_add_fields_to_product_categories_table.php @@ -0,0 +1,23 @@ +text('description')->nullable()->after('slug'); + $table->string('image')->nullable()->after('description'); + }); + } + + public function down(): void + { + Schema::table('product_categories', function (Blueprint $table) { + $table->dropColumn(['description', 'image']); + }); + } +}; diff --git a/database/migrations/2026_03_31_000600_create_banners_table.php b/database/migrations/2026_03_31_000600_create_banners_table.php new file mode 100755 index 0000000..8ed70d9 --- /dev/null +++ b/database/migrations/2026_03_31_000600_create_banners_table.php @@ -0,0 +1,27 @@ +id(); + $table->string('title'); + $table->string('image'); + $table->string('link')->nullable(); + $table->string('button_text')->nullable(); + $table->integer('order')->default(0); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('banners'); + } +}; diff --git a/database/migrations/2026_03_31_000700_create_services_table.php b/database/migrations/2026_03_31_000700_create_services_table.php new file mode 100755 index 0000000..b4e5ed0 --- /dev/null +++ b/database/migrations/2026_03_31_000700_create_services_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->string('icon')->nullable(); // Ex: bi-tools, fas fa-laptop + $table->string('image')->nullable(); + $table->integer('order')->default(0); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('services'); + } +}; diff --git a/database/migrations/2026_03_31_000800_create_visitor_logs_table.php b/database/migrations/2026_03_31_000800_create_visitor_logs_table.php new file mode 100755 index 0000000..0b13fac --- /dev/null +++ b/database/migrations/2026_03_31_000800_create_visitor_logs_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('ip_address')->nullable(); + $table->string('country')->nullable(); + $table->string('city')->nullable(); + $table->string('region')->nullable(); + $table->string('session_id')->nullable(); + $table->string('user_agent')->nullable(); + $table->string('page_url')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('visitor_logs'); + } +}; diff --git a/database/migrations/2026_04_29_001000_simplify_user_types_and_add_permissions.php b/database/migrations/2026_04_29_001000_simplify_user_types_and_add_permissions.php new file mode 100755 index 0000000..39b4f3f --- /dev/null +++ b/database/migrations/2026_04_29_001000_simplify_user_types_and_add_permissions.php @@ -0,0 +1,57 @@ +string('user_type')->change(); + }); + + // 2. Update all non-Admin users to 'User' + DB::table('users')->where('user_type', '!=', 'Admin')->update(['user_type' => 'User']); + + // 3. Define the new enum with only Admin and User + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Admin', 'User'])->default('User')->change(); + }); + + // 4. Add permissions JSON column + Schema::table('users', function (Blueprint $table) { + if (!Schema::hasColumn('users', 'permissions')) { + $table->json('permissions')->nullable()->after('preferences'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Remove permissions column + Schema::table('users', function (Blueprint $table) { + if (Schema::hasColumn('users', 'permissions')) { + $table->dropColumn('permissions'); + } + }); + + // Revert to previous enum + Schema::table('users', function (Blueprint $table) { + $table->string('user_type')->change(); + }); + + Schema::table('users', function (Blueprint $table) { + $table->enum('user_type', ['Admin', 'Member', 'Cliente', 'Fornecedor', 'User'])->default('User')->change(); + }); + } +}; diff --git a/database/migrations/2026_04_29_193600_add_public_registration_setting.php b/database/migrations/2026_04_29_193600_add_public_registration_setting.php new file mode 100755 index 0000000..a7f7c10 --- /dev/null +++ b/database/migrations/2026_04_29_193600_add_public_registration_setting.php @@ -0,0 +1,23 @@ +delete(); + } +}; diff --git a/database/migrations/2026_04_29_195200_create_user_types_table.php b/database/migrations/2026_04_29_195200_create_user_types_table.php new file mode 100755 index 0000000..c0180fc --- /dev/null +++ b/database/migrations/2026_04_29_195200_create_user_types_table.php @@ -0,0 +1,51 @@ +id(); + $table->string('name')->unique(); + $table->string('description')->nullable(); + $table->boolean('is_admin')->default(false); + $table->boolean('is_protected')->default(false); + $table->timestamps(); + }); + + // Insert default user types + \DB::table('user_types')->insert([ + [ + 'name' => 'Admin', + 'description' => 'Administrador com acesso total ao sistema', + 'is_admin' => true, + 'is_protected' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'name' => 'User', + 'description' => 'Usuário com acesso restrito por permissões', + 'is_admin' => false, + 'is_protected' => true, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_types'); + } +}; diff --git a/database/migrations/2026_05_17_193200_make_contact_responses_user_id_nullable.php b/database/migrations/2026_05_17_193200_make_contact_responses_user_id_nullable.php new file mode 100755 index 0000000..66c552e --- /dev/null +++ b/database/migrations/2026_05_17_193200_make_contact_responses_user_id_nullable.php @@ -0,0 +1,32 @@ +dropForeign(['user_id']); + $table->unsignedBigInteger('user_id')->nullable()->change(); + $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('contact_responses', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->unsignedBigInteger('user_id')->nullable(false)->change(); + $table->foreign('user_id')->references('id')->on('users'); + }); + } +}; diff --git a/database/migrations/2026_05_17_193800_create_gallery_categories_table.php b/database/migrations/2026_05_17_193800_create_gallery_categories_table.php new file mode 100755 index 0000000..9e02086 --- /dev/null +++ b/database/migrations/2026_05_17_193800_create_gallery_categories_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('name'); + $table->string('slug')->unique(); + $table->text('description')->nullable(); + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gallery_categories'); + } +}; diff --git a/database/migrations/2026_05_17_193900_add_category_id_to_galleries_table.php b/database/migrations/2026_05_17_193900_add_category_id_to_galleries_table.php new file mode 100755 index 0000000..209745b --- /dev/null +++ b/database/migrations/2026_05_17_193900_add_category_id_to_galleries_table.php @@ -0,0 +1,29 @@ +foreignId('category_id')->nullable()->after('id')->constrained('gallery_categories')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('galleries', function (Blueprint $table) { + $table->dropForeign(['category_id']); + $table->dropColumn('category_id'); + }); + } +}; diff --git a/database/migrations/2026_05_17_220000_create_news_table.php b/database/migrations/2026_05_17_220000_create_news_table.php new file mode 100755 index 0000000..6f47867 --- /dev/null +++ b/database/migrations/2026_05_17_220000_create_news_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('title'); + $table->string('slug')->unique(); + $table->text('summary')->nullable(); + $table->longText('content')->nullable(); + $table->string('featured_image')->nullable(); + $table->string('author')->nullable(); + $table->boolean('active')->default(true); + $table->timestamp('published_at')->nullable(); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); + $table->string('seo_title')->nullable(); + $table->string('seo_description')->nullable(); + $table->string('seo_keywords')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('news'); + } +}; diff --git a/database/migrations/2026_05_17_220100_create_news_images_table.php b/database/migrations/2026_05_17_220100_create_news_images_table.php new file mode 100755 index 0000000..1ccbe4a --- /dev/null +++ b/database/migrations/2026_05_17_220100_create_news_images_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('news_id')->constrained()->cascadeOnDelete(); + $table->string('file_path'); + $table->string('description')->nullable(); + $table->integer('order')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('news_images'); + } +}; diff --git a/database/migrations/2026_05_18_040000_create_chatbot_knowledge_table.php b/database/migrations/2026_05_18_040000_create_chatbot_knowledge_table.php new file mode 100755 index 0000000..e7a92ba --- /dev/null +++ b/database/migrations/2026_05_18_040000_create_chatbot_knowledge_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('title'); + $table->text('content'); + $table->string('category')->default('general'); // company_info, products, services, faq, general + $table->boolean('active')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chatbot_knowledge'); + } +}; diff --git a/database/migrations/2026_05_18_040100_create_chatbot_conversations_table.php b/database/migrations/2026_05_18_040100_create_chatbot_conversations_table.php new file mode 100755 index 0000000..9a76960 --- /dev/null +++ b/database/migrations/2026_05_18_040100_create_chatbot_conversations_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('session_id')->unique(); + $table->string('visitor_name')->nullable(); + $table->string('visitor_email')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chatbot_conversations'); + } +}; diff --git a/database/migrations/2026_05_18_040200_create_chatbot_messages_table.php b/database/migrations/2026_05_18_040200_create_chatbot_messages_table.php new file mode 100755 index 0000000..f1c5509 --- /dev/null +++ b/database/migrations/2026_05_18_040200_create_chatbot_messages_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('conversation_id')->constrained('chatbot_conversations')->cascadeOnDelete(); + $table->enum('role', ['user', 'assistant', 'system']); + $table->text('content'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chatbot_messages'); + } +}; diff --git a/database/migrations/2026_05_18_043000_add_restrictions_to_chatbot_knowledge_table.php b/database/migrations/2026_05_18_043000_add_restrictions_to_chatbot_knowledge_table.php new file mode 100755 index 0000000..9e80c6b --- /dev/null +++ b/database/migrations/2026_05_18_043000_add_restrictions_to_chatbot_knowledge_table.php @@ -0,0 +1,28 @@ +text('restrictions')->nullable()->after('content'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chatbot_knowledge', function (Blueprint $table) { + $table->dropColumn('restrictions'); + }); + } +}; diff --git a/database/migrations/2026_05_18_050000_add_visitor_fields_to_chatbot_conversations_table.php b/database/migrations/2026_05_18_050000_add_visitor_fields_to_chatbot_conversations_table.php new file mode 100755 index 0000000..4274e94 --- /dev/null +++ b/database/migrations/2026_05_18_050000_add_visitor_fields_to_chatbot_conversations_table.php @@ -0,0 +1,29 @@ +string('visitor_phone')->nullable()->after('visitor_email'); + $table->string('visitor_city')->nullable()->after('visitor_phone'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chatbot_conversations', function (Blueprint $table) { + $table->dropColumn(['visitor_phone', 'visitor_city']); + }); + } +}; diff --git a/database/migrations/2026_05_18_051000_add_tokens_used_to_chatbot_messages_table.php b/database/migrations/2026_05_18_051000_add_tokens_used_to_chatbot_messages_table.php new file mode 100755 index 0000000..60954d5 --- /dev/null +++ b/database/migrations/2026_05_18_051000_add_tokens_used_to_chatbot_messages_table.php @@ -0,0 +1,28 @@ +integer('tokens_used')->nullable()->after('content'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chatbot_messages', function (Blueprint $table) { + $table->dropColumn('tokens_used'); + }); + } +}; diff --git a/database/migrations/2026_05_18_060000_add_status_and_rating_to_chatbot_conversations_table.php b/database/migrations/2026_05_18_060000_add_status_and_rating_to_chatbot_conversations_table.php new file mode 100755 index 0000000..577a0f7 --- /dev/null +++ b/database/migrations/2026_05_18_060000_add_status_and_rating_to_chatbot_conversations_table.php @@ -0,0 +1,31 @@ +string('status', 20)->default('active')->after('visitor_city'); + $table->tinyInteger('rating')->nullable()->after('status'); + $table->text('rating_comment')->nullable()->after('rating'); + $table->timestamp('ended_at')->nullable()->after('rating_comment'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chatbot_conversations', function (Blueprint $table) { + $table->dropColumn(['status', 'rating', 'rating_comment', 'ended_at']); + }); + } +}; diff --git a/database/migrations/2026_06_04_200000_create_chatbot_rules_table.php b/database/migrations/2026_06_04_200000_create_chatbot_rules_table.php new file mode 100755 index 0000000..1f2aca5 --- /dev/null +++ b/database/migrations/2026_06_04_200000_create_chatbot_rules_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('title', 255); + $table->text('content'); + $table->boolean('active')->default(true); + $table->unsignedSmallInteger('order')->default(0)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('chatbot_rules'); + } +}; diff --git a/database/seeders/AdminUserSeeder.php b/database/seeders/AdminUserSeeder.php new file mode 100755 index 0000000..9de9f0a --- /dev/null +++ b/database/seeders/AdminUserSeeder.php @@ -0,0 +1,26 @@ + 'admin@admin.com'], + [ + 'name' => 'Administrador', + 'password' => Hash::make('admin123'), + 'user_type' => 'Admin', + 'email_verified_at' => now(), + ] + ); + } +} diff --git a/database/seeders/ContactMessageSeeder.php b/database/seeders/ContactMessageSeeder.php new file mode 100755 index 0000000..dd046e8 --- /dev/null +++ b/database/seeders/ContactMessageSeeder.php @@ -0,0 +1,57 @@ +isEmpty()) { + $this->call(ContactSubjectSeeder::class); + $subjects = ContactSubject::all(); + } + + $names = [ + 'Ricardo Oliveira', 'Mariana Santos', 'Carlos Eduardo', 'Ana Beatriz', + 'Fernando Souza', 'Juliana Lima', 'Marcos Rocha', 'Patrícia Gomes', + 'Luiz Henrique', 'Sofia Almeida' + ]; + + $messages = [ + 'Gostaria de um orçamento para manutenção de 5 computadores na minha empresa.', + 'Vocês têm notebooks da marca Dell em estoque? Qual o valor médio?', + 'Estou precisando de uma placa de vídeo específica para jogos, vocês trabalham com encomendas?', + 'Meu computador não liga mais após uma queda de energia. Vocês fazem busca em domicílio?', + 'Quais os horários disponíveis para uma visita técnica nesta semana?', + 'Gostaria de saber se vocês fazem instalação de redes cabeadas para escritórios.', + 'Preciso formatar meu notebook e fazer backup de arquivos importantes.', + 'Vocês vendem licenças originais do Windows e Office?', + 'Meu teclado parou de funcionar algumas teclas, vocês fazem a troca?', + 'Gostaria de informações sobre o plano de manutenção mensal para empresas.' + ]; + + foreach ($names as $index => $name) { + ContactMessage::create([ + 'name' => $name, + 'email' => Str::lower(Str::slug($name)) . '@exemplo.com', + 'company' => $index % 2 == 0 ? 'Empresa ' . ($index + 1) : null, + 'website' => $index % 3 == 0 ? 'https://empresa' . ($index + 1) . '.com.br' : null, + 'whatsapp' => '(11) 9' . rand(7000, 9999) . '-' . rand(1000, 9999), + 'subject_id' => $subjects->random()->id, + 'message' => $messages[$index], + 'read' => rand(0, 1), + 'created_at' => now()->subDays(rand(0, 30))->subHours(rand(0, 23)), + ]); + } + } +} diff --git a/database/seeders/ContactSubjectSeeder.php b/database/seeders/ContactSubjectSeeder.php new file mode 100755 index 0000000..b7d5ae5 --- /dev/null +++ b/database/seeders/ContactSubjectSeeder.php @@ -0,0 +1,27 @@ + $name]); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100755 index 0000000..2f5c7f9 --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +call([ + AdminUserSeeder::class, + ContactSubjectSeeder::class, + ]); + } +} diff --git a/database/seeders/FaqSeeder.php b/database/seeders/FaqSeeder.php new file mode 100755 index 0000000..ba4d3b0 --- /dev/null +++ b/database/seeders/FaqSeeder.php @@ -0,0 +1,42 @@ + 'Qual o prazo médio para manutenção de notebooks?', + 'answer' => 'O prazo médio é de 2 a 5 dias úteis, dependendo da complexidade do reparo e da disponibilidade de peças.', + 'order' => 1, + ], + [ + 'question' => 'Vocês oferecem garantia nos serviços?', + 'answer' => 'Sim, todos os nossos serviços possuem garantia de 90 dias, conforme o Código de Defesa do Consumidor.', + 'order' => 2, + ], + [ + 'question' => 'Fazem orçamento sem compromisso?', + 'answer' => 'Sim! Realizamos a avaliação técnica inicial sem custo para o cliente.', + 'order' => 3, + ], + [ + 'question' => 'Trabalham com retirada e entrega?', + 'answer' => 'Sim, possuímos serviço de leva e traz para sua maior comodidade em toda a região central.', + 'order' => 4, + ], + ]; + + foreach ($faqs as $faq) { + Faq::updateOrCreate(['question' => $faq['question']], $faq); + } + } +} diff --git a/database/seeders/PageSeeder.php b/database/seeders/PageSeeder.php new file mode 100755 index 0000000..0e419dc --- /dev/null +++ b/database/seeders/PageSeeder.php @@ -0,0 +1,187 @@ + 'index', + 'title' => 'Início', + 'meta_description' => 'Bem-vindo à Syspanel - Tecnologia com Confiança.', + 'content' => ' +
+

Tecnologia com Confiança

+

Manutenção especializada de computadores, notebooks e venda de acessórios com as melhores marcas.

+ +
+
+
+
+ +

Manutenção

+

Especialistas em notebooks e desktops de todas as marcas.

+
+
+
+
+ +

Vendas

+

Notebooks novos e seminovos revisados com garantia.

+
+
+
+
+ +

Confiança

+

Transparência total em orçamentos e peças originais.

+
+
+
+ ', + ], + [ + 'slug' => 'about', + 'title' => 'Sobre Nós', + 'meta_description' => 'Conheça a história e os valores da Syspanel.', + 'content' => ' +
+
+ Equipe +
+
+

Syspanel: Sua parceira em tecnologia

+

Há mais de 10 anos no mercado, somos referência em soluções de informática.

+

Nascemos da paixão pela tecnologia e da necessidade de oferecer um serviço de manutenção transparente e qualificado.

+
+
+ ', + ], + [ + 'slug' => 'produtos', + 'title' => 'Produtos', + 'meta_description' => 'Confira nossa linha de produtos e acessórios.', + 'content' => ' +

Produtos em Destaque

+
+
+
+
Notebooks Novos
+

As melhores marcas do mercado.

+
+
+
+
+
Acessórios
+

Periféricos de alta qualidade.

+
+
+
+
+
Peças
+

Upgrades e reposição.

+
+
+
+ ', + ], + [ + 'slug' => 'servicos', + 'title' => 'Serviços', + 'meta_description' => 'Conheça nossos serviços de manutenção e consultoria.', + 'content' => ' +

O que podemos fazer por você?

+
+
+
+

Manutenção de Notebooks

+

Troca de telas, baterias e reparos em placa-mãe.

+
+
+
+
+

Computadores e Redes

+

Upgrade de hardware e configuração de redes Wi-Fi.

+
+
+
+ ', + ], + [ + 'slug' => 'assistentes-digitais-com-ia', + 'title' => 'Assistentes Digitais com IA', + 'meta_description' => 'Descubra como os assistentes digitais com IA podem transformar sua empresa.', + 'content' => 'Conteúdo dinâmico em assistentes-digitais-ia.blade.php', + ], + [ + 'slug' => 'linux-servers', + 'title' => 'Servidores Linux', + 'meta_description' => 'Implantação de servidores Linux robustos, seguros e de alta performance para sua empresa.', + 'content' => 'Conteúdo dinâmico em servidores-linux.blade.php', + ], + [ + 'slug' => 'erp', + 'title' => 'ERP Syspanel', + 'meta_description' => 'Descubra como o ERP Syspanel pode transformar a gestão da sua empresa com módulos personalizados e inteligência artificial.', + 'content' => 'Conteúdo dinâmico em erp.blade.php', + ], + [ + 'slug' => 'fluxo-de-caixa-pro', + 'title' => 'Fluxo de Caixa Pro', + 'meta_description' => 'Domine as finanças da sua empresa com o Fluxo de Caixa Pro. Controle total, DRE automático, IA financeira e acesso de qualquer lugar.', + 'content' => 'Conteúdo dinâmico em fluxo-de-caixa-pro.blade.php', + ], + [ + 'slug' => 'downloads', + 'title' => 'Downloads', + 'meta_description' => 'Baixe drivers e utilitários úteis.', + 'content' => ' +

Downloads Recomendados

+ + ', + ], + [ + 'slug' => 'contato', + 'title' => 'Contato', + 'meta_description' => 'Fale conosco para orçamentos e suporte técnico.', + 'content' => null, + ], + [ + 'slug' => 'faq', + 'title' => 'FAQ', + 'meta_description' => 'Perguntas frequentes sobre nossos serviços.', + 'content' => null, + ], + [ + 'slug' => 'politica-privacidade', + 'title' => 'Política de Privacidade', + 'meta_description' => 'Conheça nossos termos de uso e política de privacidade em conformidade com a LGPD.', + 'content' => 'Conteúdo dinâmico em politica-privacidade.blade.php', + ], + ]; + + foreach ($pages as $page) { + Page::updateOrCreate(['slug' => $page['slug']], $page); + } + } +} diff --git a/database/seeders/ProductCategorySeeder.php b/database/seeders/ProductCategorySeeder.php new file mode 100755 index 0000000..5032955 --- /dev/null +++ b/database/seeders/ProductCategorySeeder.php @@ -0,0 +1,25 @@ + 'Notebooks', 'slug' => 'notebooks', 'active' => true, 'order' => 1], + ['name' => 'Acessórios', 'slug' => 'acessorios', 'active' => true, 'order' => 2], + ['name' => 'Peças', 'slug' => 'pecas', 'active' => true, 'order' => 3], + ]; + + foreach ($categories as $category) { + ProductCategory::updateOrCreate(['slug' => $category['slug']], $category); + } + } +} diff --git a/database/seeders/ServiceSeeder.php b/database/seeders/ServiceSeeder.php new file mode 100755 index 0000000..4a4287c --- /dev/null +++ b/database/seeders/ServiceSeeder.php @@ -0,0 +1,70 @@ + 'Manutenção de Notebooks', + 'slug' => 'manutencao-notebooks', + 'description' => 'Reparo especializado em hardware, troca de telas, teclados e baterias.', + 'icon' => 'bi bi-laptop', + 'active' => true, + 'order' => 1 + ], + [ + 'name' => 'Redes e Wi-Fi', + 'slug' => 'redes-wifi', + 'description' => 'Configuração de roteadores, repetidores e infraestrutura de rede cabeada.', + 'icon' => 'bi bi-wifi', + 'active' => true, + 'order' => 2 + ], + [ + 'name' => 'Limpeza Preventiva', + 'slug' => 'limpeza-preventiva', + 'description' => 'Limpeza interna completa e troca de pasta térmica para evitar superaquecimento.', + 'icon' => 'bi bi-wind', + 'active' => true, + 'order' => 3 + ], + [ + 'name' => 'Servidores Linux', + 'slug' => 'servidores-linux', + 'description' => 'Implantação e configuração de servidores Linux em infraestrutura local ou nuvem com alta disponibilidade.', + 'icon' => 'bi bi-hdd-network', + 'active' => true, + 'order' => 4 + ], + [ + 'name' => 'Assistentes Digitais com IA', + 'slug' => 'assistentes-digitais-ia', + 'description' => 'Implantação de assistentes digitais inteligentes com IA para automatizar atendimento e processos internos.', + 'icon' => 'bi bi-robot', + 'active' => true, + 'order' => 5 + ], + [ + 'name' => 'Registro de Marcas no INPI', + 'slug' => 'registro-marcas-inpi', + 'description' => 'Proteção legal da sua marca junto ao INPI, garantindo exclusividade de uso em todo território nacional.', + 'icon' => 'bi bi-shield-check', + 'active' => true, + 'order' => 6 + ], + ]; + + foreach ($services as $service) { + Service::updateOrCreate(['slug' => $service['slug']], $service); + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..96e24f8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1912 @@ +{ + "name": "html", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "axios": "^1.11.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^3.0.0", + "tailwindcss": "^4.0.0", + "vite": "^8.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.0.0.tgz", + "integrity": "sha512-eO6B/IPH448XNjyA5OmJsFAeJH3MeQGWodNMzejx44+rhV44cs0op05t4yku9ifaRnikDkSHLOLXszJL0tiW3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "tinyglobby": "^0.2.12", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^8.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/vite": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..d6b90b0 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "devDependencies": { + "@popperjs/core": "^2.11.6", + "@tailwindcss/vite": "^4.0.0", + "axios": "^1.11.0", + "bootstrap": "^5.2.3", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^3.0.0", + "sass": "^1.56.1", + "tailwindcss": "^4.0.0", + "vite": "^8.0.0" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 0000000..e7f0a48 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100755 index 0000000..b574a59 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100755 index 0000000..ee8f07e --- /dev/null +++ b/public/index.php @@ -0,0 +1,20 @@ +handleRequest(Request::capture()); diff --git a/public/robots.txt b/public/robots.txt new file mode 100755 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100755 index 0000000..92f32c0 --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,6 @@ +/* @import 'tailwindcss'; */ + +/* @theme { + --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; +} */ diff --git a/resources/js/app.js b/resources/js/app.js new file mode 100755 index 0000000..e59d6a0 --- /dev/null +++ b/resources/js/app.js @@ -0,0 +1 @@ +import './bootstrap'; diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js new file mode 100755 index 0000000..46f7a33 --- /dev/null +++ b/resources/js/bootstrap.js @@ -0,0 +1,34 @@ +import 'bootstrap'; + +/** + * We'll load the axios HTTP library which allows us to easily issue requests + * to our Laravel back-end. This library automatically handles sending the + * CSRF token as a header based on the value of the "XSRF" token cookie. + */ + +import axios from 'axios'; +window.axios = axios; + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + +/** + * Echo exposes an expressive API for subscribing to channels and listening + * for events that are broadcast by Laravel. Echo and event broadcasting + * allows your team to easily build robust real-time web applications. + */ + +// import Echo from 'laravel-echo'; + +// import Pusher from 'pusher-js'; +// window.Pusher = Pusher; + +// window.Echo = new Echo({ +// broadcaster: 'pusher', +// key: import.meta.env.VITE_PUSHER_APP_KEY, +// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', +// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, +// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, +// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, +// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', +// enabledTransports: ['ws', 'wss'], +// }); diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss new file mode 100755 index 0000000..172daaa --- /dev/null +++ b/resources/sass/_variables.scss @@ -0,0 +1,7 @@ +// Body +$body-bg: #f8fafc; + +// Typography +$font-family-sans-serif: 'Nunito', sans-serif; +$font-size-base: 0.9rem; +$line-height-base: 1.6; diff --git a/resources/sass/app.scss b/resources/sass/app.scss new file mode 100755 index 0000000..1026a0b --- /dev/null +++ b/resources/sass/app.scss @@ -0,0 +1,8 @@ +// Fonts +@import url('https://fonts.bunny.net/css?family=Nunito'); + +// Variables +@import 'variables'; + +// Bootstrap +@import 'bootstrap/scss/bootstrap'; diff --git a/resources/views/about.blade.php b/resources/views/about.blade.php new file mode 100755 index 0000000..e48bda9 --- /dev/null +++ b/resources/views/about.blade.php @@ -0,0 +1,15 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') +
+ {!! $page->content !!} +
+@endsection diff --git a/resources/views/admin/banners/create.blade.php b/resources/views/admin/banners/create.blade.php new file mode 100755 index 0000000..c6e62c6 --- /dev/null +++ b/resources/views/admin/banners/create.blade.php @@ -0,0 +1,72 @@ +@extends('layouts.admin') + +@section('title', 'Novo Banner') + +@section('content') +
+

Cadastrar Banner

+ + Voltar + +
+ +
+
+
Informações do Banner
+
+
+
+ @csrf + +
+
+
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('link')
{{ $message }}
@enderror +
+ +
+ + + @error('button_text')
{{ $message }}
@enderror +
+
+
+
+ + + Recomendado: 1920x600px para melhor visualização. + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/banners/edit.blade.php b/resources/views/admin/banners/edit.blade.php new file mode 100755 index 0000000..3429bd8 --- /dev/null +++ b/resources/views/admin/banners/edit.blade.php @@ -0,0 +1,87 @@ +@extends('layouts.admin') + +@section('title', 'Editar Banner') + +@section('content') + + +
+

Editar Banner: {{ $banner->title }}

+ + Voltar + +
+ +
+
+
Informações do Banner
+
+
+
+ @csrf + @method('PUT') + +
+
+
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('link')
{{ $message }}
@enderror +
+ +
+ + + @error('button_text')
{{ $message }}
@enderror +
+
+
+
+ + @if($banner->image) +
+ +
+ @endif + + Deixe em branco para manter a imagem atual. + @error('image')
{{ $message }}
@enderror +
+ +
+ + + @error('order')
{{ $message }}
@enderror +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/banners/index.blade.php b/resources/views/admin/banners/index.blade.php new file mode 100755 index 0000000..2d6e831 --- /dev/null +++ b/resources/views/admin/banners/index.blade.php @@ -0,0 +1,80 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Banners') + +@section('content') +
+

Gerenciar Banners (Destaques)

+ + Novo Banner + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Banners Ativos e Inativos
+
+
+
+ + + + + + + + + + + + + @forelse($banners as $banner) + + + + + + + + + @empty + + + + @endforelse + +
OrdemBannerTítuloBotão / LinkStatusAções
{{ $banner->order }} + + {{ $banner->title }} + @if($banner->button_text) + {{ $banner->button_text }} + @endif +
+ {{ $banner->link ?: 'Sem link' }} +
+ + {{ $banner->active ? 'Ativo' : 'Inativo' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
Nenhum banner encontrado.
+
+
+ {{ $banners->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/contacts/index.blade.php b/resources/views/admin/contacts/index.blade.php new file mode 100755 index 0000000..1c8889b --- /dev/null +++ b/resources/views/admin/contacts/index.blade.php @@ -0,0 +1,67 @@ +@extends('layouts.admin') + +@section('title', 'Mensagens de Contato') + +@section('content') +
+

Contatos Recebidos

+
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Mensagens
+
+
+
+ + + + + + + + + + + + + @foreach($contacts as $contact) + + + + + + + + + @endforeach + +
StatusDataNomeAssuntoWhatsAppAções
+ @if($contact->read) + + @else + + @endif + {{ $contact->created_at->format('d/m/Y H:i') }}{{ $contact->name }}{{ $contact->subject->name ?? 'N/A' }}{{ $contact->whatsapp ?? '-' }} + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $contacts->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/contacts/show.blade.php b/resources/views/admin/contacts/show.blade.php new file mode 100755 index 0000000..f695400 --- /dev/null +++ b/resources/views/admin/contacts/show.blade.php @@ -0,0 +1,152 @@ +@extends('layouts.admin') + +@section('title', 'Detalhes do Contato') + +@section('content') +
+

Mensagem de {{ $contact->name }}

+ + Voltar + +
+ +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +
+
+ +
+
+
Conteúdo da Solicitação
+ {{ $contact->created_at->format('d/m/Y \à\s H:i') }} +
+
+
+
+

Nome

+

{{ $contact->name }}

+
+
+

E-mail

+

{{ $contact->email }}

+
+
+ +
+
+

Empresa

+

{{ $contact->company ?? 'Não informada' }}

+
+
+

Site

+

+ @if($contact->website) + {{ $contact->website }} + @else + Não informado + @endif +

+
+
+

WhatsApp

+

+ @if($contact->whatsapp) + + {{ $contact->whatsapp }} + + @else + Não informado + @endif +

+
+
+ +
+
+

Assunto

+

{{ $contact->subject->name ?? 'Geral' }}

+
+
+ +
+ +
+
+

Mensagem do Cliente

+
{{ $contact->message }}
+
+
+
+
+ + +
+
+
Histórico de Respostas
+
+
+ @forelse($contact->responses as $response) +
+
+ {{ $response->user->name }} + {{ $response->created_at->format('d/m/Y H:i') }} +
+
+ {{ $response->message }} +
+
+ @empty +

Nenhuma resposta enviada ainda.

+ @endforelse +
+
+
+ +
+ +
+
+
Enviar Resposta
+
+
+
+ @csrf +
+ + + @error('message')
{{ $message }}
@enderror + Esta mensagem será enviada para {{ $contact->email }}. +
+ + +
+
+
+ + +
+
+ @if($contact->whatsapp) + + Chamar no WhatsApp + + @endif + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/downloads/create.blade.php b/resources/views/admin/downloads/create.blade.php new file mode 100755 index 0000000..b271d0b --- /dev/null +++ b/resources/views/admin/downloads/create.blade.php @@ -0,0 +1,62 @@ +@extends('layouts.admin') + +@section('title', 'Novo Download') + +@section('content') +
+

Novo Download

+ + Voltar + +
+ +
+
+
Upload de Arquivo
+
+
+
+ @csrf + +
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+ +
+
+
+ + + @error('file_type')
{{ $message }}
@enderror +
+
+
+
+ + + @error('file')
{{ $message }}
@enderror +
+
+
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/downloads/edit.blade.php b/resources/views/admin/downloads/edit.blade.php new file mode 100755 index 0000000..a66fd1e --- /dev/null +++ b/resources/views/admin/downloads/edit.blade.php @@ -0,0 +1,73 @@ +@extends('layouts.admin') + +@section('title', 'Editar Download') + +@section('content') +
+

Editar Download

+ + Voltar + +
+ +
+
+
Informações do Arquivo
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+ +
+
+
+ + + @error('file_type')
{{ $message }}
@enderror +
+
+
+
+ + + Deixe em branco para manter o arquivo atual. + @error('file')
{{ $message }}
@enderror +
+
+
+
+ + +
+
+
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/downloads/index.blade.php b/resources/views/admin/downloads/index.blade.php new file mode 100755 index 0000000..fbfb239 --- /dev/null +++ b/resources/views/admin/downloads/index.blade.php @@ -0,0 +1,66 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Downloads') + +@section('content') +
+

Gerenciamento de Downloads

+ + Novo Download + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Arquivos Disponíveis
+
+
+
+ + + + + + + + + + + + @foreach($downloads as $download) + + + + + + + + @endforeach + +
TítuloTipoStatusBaixadoAções
{{ $download->title }}{{ $download->file_type }} + + {{ $download->active ? 'Ativo' : 'Inativo' }} + + {{ $download->download_count }} vezes + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $downloads->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/erp/ad.blade.php b/resources/views/admin/erp/ad.blade.php new file mode 100755 index 0000000..495516a --- /dev/null +++ b/resources/views/admin/erp/ad.blade.php @@ -0,0 +1,240 @@ +@extends('layouts.admin') + +@section('title', 'Conheça o ERP Syspanel') + +@section('content') +
+ +
+
+
+
+
+
+

Potencialize sua Empresa com o ERP Syspanel

+

A solução completa e inteligente para a gestão total do seu negócio. Do financeiro à Inteligência Artificial, tudo em um só lugar.

+ + Assine Agora o ERP Syspanel + +
+
+ +
+
+
+
+
+
+ + +
+

Módulos Especializados para seu Crescimento

+ +
+ + +
+ +
+
+
+
+
+
Módulo ERP
+
Financeiro
+

Gestão de contas a pagar, receber e movimentações bancárias em tempo real.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
CRM
+

Relacionamento completo: Leads, Oportunidades, Atividades e Pipelines de venda.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
Gestão de Projetos
+

Organize tarefas e prazos com metodologia Kanban e controle de produtividade.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
Estoque e Matérias-Primas
+

Controle total de insumos, matérias-primas e armazéns com inteligência.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo IA
+
Agente de IA ERP
+

Analise seus dados em tempo real com um agente inteligente exclusivo.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
Produção (PCP)
+

Planejamento e controle rigoroso da produção e engenharia de produto (BOM).

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
Veículos e Frotas
+

Gestão de motoristas, ordens de serviço e manutenção de frotas da empresa.

+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Módulo ERP
+
Gestão da Qualidade
+

Controle total de Manuais, Procedimentos e Formulários (SGQ).

+
+
+ +
+
+
+
+
+
+ + +
+
+
Outros Módulos Inclusos
+
+
+
+
+
    +
  • RH (Recursos Humanos)
  • +
  • Vendas ao Cliente
  • +
  • Gestão de Fornecedores
  • +
+
+
+
    +
  • Transportadoras e Fretes
  • +
  • Service Tags
  • +
  • Gerência Comercial
  • +
+
+
+
    +
  • Plano de Negócios IA
  • +
  • Assistente Local
  • +
  • Gestão de Organogramas
  • +
+
+
+
+
+ + +
+

Pronto para transformar sua gestão?

+

Junte-se a centenas de empresas que já utilizam o ecossistema Syspanel.

+ + Quero conhecer o ERP Syspanel agora! + +
+ Acesse syspanel.com.br para mais informações. +
+
+
+@endsection + +@push('css') + +@endpush diff --git a/resources/views/admin/faqs/create.blade.php b/resources/views/admin/faqs/create.blade.php new file mode 100755 index 0000000..38514fe --- /dev/null +++ b/resources/views/admin/faqs/create.blade.php @@ -0,0 +1,47 @@ +@extends('layouts.admin') + +@section('title', 'Nova Pergunta FAQ') + +@section('content') +
+

Nova Pergunta FAQ

+ + Voltar + +
+ +
+
+
Criar Pergunta e Resposta
+
+
+
+ @csrf + +
+ + + @error('question')
{{ $message }}
@enderror +
+ +
+ + + @error('answer')
{{ $message }}
@enderror +
+ +
+ + + Números menores aparecem primeiro. + @error('order')
{{ $message }}
@enderror +
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/faqs/edit.blade.php b/resources/views/admin/faqs/edit.blade.php new file mode 100755 index 0000000..a5bcf67 --- /dev/null +++ b/resources/views/admin/faqs/edit.blade.php @@ -0,0 +1,60 @@ +@extends('layouts.admin') + +@section('title', 'Editar FAQ') + +@section('content') +
+

Editar FAQ

+ + Voltar + +
+ +
+
+
Editar Pergunta e Resposta
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('question')
{{ $message }}
@enderror +
+ +
+ + + @error('answer')
{{ $message }}
@enderror +
+ +
+
+
+ + + @error('order')
{{ $message }}
@enderror +
+
+
+
+ + +
+
+
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/faqs/index.blade.php b/resources/views/admin/faqs/index.blade.php new file mode 100755 index 0000000..76f6e00 --- /dev/null +++ b/resources/views/admin/faqs/index.blade.php @@ -0,0 +1,64 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar FAQ') + +@section('content') +
+

Perguntas Frequentes (FAQ)

+ + Nova Pergunta + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Perguntas
+
+
+
+ + + + + + + + + + + @foreach($faqs as $faq) + + + + + + + @endforeach + +
OrdemPerguntaStatusAções
{{ $faq->order }}{{ $faq->question }} + + {{ $faq->active ? 'Ativa' : 'Inativa' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $faqs->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/galleries/create.blade.php b/resources/views/admin/galleries/create.blade.php new file mode 100755 index 0000000..df4a566 --- /dev/null +++ b/resources/views/admin/galleries/create.blade.php @@ -0,0 +1,67 @@ +@extends('layouts.admin') + +@section('title', 'Nova Galeria') + +@section('content') +
+

Criar Galeria

+ + Voltar + +
+ +
+
+
Informações Básicas
+
+
+
+ @csrf + +
+ + +
+ +
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + +
+ +
+ +
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/admin/galleries/edit.blade.php b/resources/views/admin/galleries/edit.blade.php new file mode 100755 index 0000000..9a45397 --- /dev/null +++ b/resources/views/admin/galleries/edit.blade.php @@ -0,0 +1,124 @@ +@extends('layouts.admin') + +@section('title', 'Editar Galeria') + +@section('content') +
+

Gerenciar Galeria: {{ $gallery->title }}

+ + Voltar + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+ +
+
+
+
Configurações
+
+
+
+ @csrf + @method('PUT') +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+
Adicionar Fotos
+
+
+
+ @csrf +
+ + + Selecione uma ou mais imagens (máx. 5MB cada). +
+ +
+
+
+
+ + +
+
+
+
Fotos na Galeria
+
+
+
+ @forelse($gallery->images as $img) +
+
+ +
+
+ @csrf + @method('PUT') +
+ +
+
+ + +
+
+
+ @csrf + @method('DELETE') + +
+
+
+
+ @empty +
+

Nenhuma foto adicionada ainda.

+
+ @endforelse +
+
+
+
+
+@endsection diff --git a/resources/views/admin/galleries/index.blade.php b/resources/views/admin/galleries/index.blade.php new file mode 100755 index 0000000..ea99e22 --- /dev/null +++ b/resources/views/admin/galleries/index.blade.php @@ -0,0 +1,71 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Galerias') + +@section('content') +
+

Galerias de Imagens

+ + Nova Galeria + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Galerias
+
+
+
+ + + + + + + + + + + + + @foreach($galleries as $gallery) + + + + + + + + + @endforeach + +
TítuloCategoriaURL (Slug)ImagensStatusAções
{{ $gallery->title }}{{ $gallery->category?->name ?? '—' }}/galeria/{{ $gallery->slug }}{{ $gallery->images_count }} fotos + + {{ $gallery->active ? 'Ativa' : 'Inativa' }} + + + + Gerenciar Fotos + + + Ver + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $galleries->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/gallery-categories/create.blade.php b/resources/views/admin/gallery-categories/create.blade.php new file mode 100755 index 0000000..88a5612 --- /dev/null +++ b/resources/views/admin/gallery-categories/create.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.admin') + +@section('title', 'Nova Categoria de Galeria') + +@section('content') +
+

Criar Categoria

+ + Voltar + +
+ +
+
+
Informações da Categoria
+
+
+
+ @csrf + +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + +
+ +
+ +
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/admin/gallery-categories/edit.blade.php b/resources/views/admin/gallery-categories/edit.blade.php new file mode 100755 index 0000000..0b32197 --- /dev/null +++ b/resources/views/admin/gallery-categories/edit.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.admin') + +@section('title', 'Editar Categoria de Galeria') + +@section('content') +
+

Editar Categoria: {{ $galleryCategory->name }}

+ + Voltar + +
+ +
+
+
Informações da Categoria
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+@endsection diff --git a/resources/views/admin/gallery-categories/index.blade.php b/resources/views/admin/gallery-categories/index.blade.php new file mode 100755 index 0000000..726bd87 --- /dev/null +++ b/resources/views/admin/gallery-categories/index.blade.php @@ -0,0 +1,66 @@ +@extends('layouts.admin') + +@section('title', 'Categorias de Galerias') + +@section('content') +
+

Categorias de Galerias

+ + Nova Categoria + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Categorias
+
+
+
+ + + + + + + + + + + + @foreach($categories as $category) + + + + + + + + @endforeach + +
NomeSlugGaleriasStatusAções
{{ $category->name }}{{ $category->slug }}{{ $category->galleries_count }} + + {{ $category->active ? 'Ativa' : 'Inativa' }} + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $categories->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/manual/index.blade.php b/resources/views/admin/manual/index.blade.php new file mode 100755 index 0000000..04d9bee --- /dev/null +++ b/resources/views/admin/manual/index.blade.php @@ -0,0 +1,601 @@ +@extends('layouts.admin') + +@section('title', 'Manual de Uso') + +@section('content') +
+ + +
+

Manual de Uso do Sistema

+
+ + + + + +
+
+
1. Acesso ao Painel Administrativo
+
+
+

Para acessar o painel administrativo do seu site:

+
    +
  1. Acesse o endereço http://seusite.com.br/login
  2. +
  3. Insira seu e-mail e senha cadastrados
  4. +
  5. Clique em "Entrar"
  6. +
  7. Após o login, você será redirecionado para o Dashboard
  8. +
+
+ + Primeiro acesso? Altere sua senha imediatamente em Configurações > Perfil. +
+
+
+ + +
+
+
2. Dashboard
+
+
+

O Dashboard é a página inicial do painel administrativo. Ele apresenta:

+
    +
  • Métricas rápidas: Total de usuários, produtos, serviços, galerias, páginas e notícias
  • +
  • Atalhos rápidos: Links para as funcionalidades mais utilizadas
  • +
  • Sugestão de funcionalidades: Use o formulário "Sugerir Funcionalidade" para enviar solicitações ao suporte
  • +
+
+
+ + +
+
+
3. Gerenciamento de Usuários
+
+
+

Disponível apenas para administradores.

+

No menu Usuários, você pode:

+
    +
  • Listar todos os usuários cadastrados no sistema
  • +
  • Criar novos usuários com tipo Admin ou Usuário comum
  • +
  • Editar dados de usuários existentes (nome, e-mail, tipo, avatar)
  • +
  • Excluir usuários (exceto o administrador principal)
  • +
+ +
Tipos de Usuário
+
+ + + + + + + + + + + + + + + + + +
TipoDescrição
AdminAcesso total ao painel, incluindo gerenciamento de usuários, tipos de usuário e todas as permissões
UserAcesso limitado baseado em permissões específicas atribuídas pelo administrador
+
+ +
Tipos de Usuário Personalizados
+

No menu Tipos de Usuário, você pode criar tipos personalizados com combinações específicas de permissões para diferentes perfis de acesso.

+
+
+ + +
+
+
4. Permissões de Acesso
+
+
+

O sistema utiliza um modelo de permissões para controlar o acesso dos usuários. Ao editar um usuário, você pode atribuir permissões individuais:

+
+ + + + + + + + + + + + + + + + + + + + + +
PermissãoDescriçãoSeção no Painel
pagesGerenciar páginas institucionaisPáginas
newsGerenciar notíciasNotícias
galleriesGerenciar galerias e categoriasGalerias
productsGerenciar produtos e categoriasProdutos
servicesGerenciar serviçosServiços
contactsVisualizar e responder mensagens de contatoContatos
subjectsGerenciar assuntos do formulário de contatoAssuntos
faqsGerenciar perguntas frequentesFAQ
downloadsGerenciar arquivos para downloadDownloads
marketingAcessar ferramentas de marketing, analytics e bannersMarketing
settingsAlterar configurações globais do siteDados da Empresa
+
+
+ + Atenção: Administradores possuem acesso total automaticamente, sem necessidade de permissões individuais. +
+
+
+ + +
+
+
5. Mensagens de Contato
+
+
+

Todas as mensagens enviadas pelo formulário de contato do site são listadas no menu Contatos.

+
Funcionalidades:
+
    +
  • Visualizar: Clique em uma mensagem para ver os detalhes completos (nome, e-mail, assunto, mensagem)
  • +
  • Responder: Digite sua resposta diretamente no painel. O cliente receberá um e-mail formatado com a resposta
  • +
  • Excluir: Remova mensagens antigas ou irrelevantes
  • +
+
+ + Dica: Configure os assuntos do formulário de contato no menu Assuntos para organizar melhor as mensagens recebidas (ex: Orçamento, Suporte, Comercial). +
+
+
+ + +
+
+
6. Assuntos de Contato
+
+
+

No menu Assuntos, você gerencia as opções de assunto que aparecem no formulário de contato do site.

+
    +
  • Criar: Adicione novos assuntos (ex: "Orçamento", "Suporte Técnico", "Parceria")
  • +
  • Editar: Altere o nome ou a descrição dos assuntos existentes
  • +
  • Ativar/Desativar: Assuntos desativados não aparecem no formulário, mas o histórico é mantido
  • +
  • Excluir: Remova assuntos não utilizados
  • +
+
+
+ + +
+
+
7. Serviços
+
+
+

No menu Serviços, você gerencia os serviços oferecidos pela empresa.

+
Como cadastrar um serviço:
+
    +
  1. Clique em "Novo Serviço"
  2. +
  3. Preencha o nome do serviço
  4. +
  5. Adicione uma descrição detalhada (suporta formatação HTML)
  6. +
  7. Defina se o serviço está ativo ou inativo
  8. +
  9. Marque como destaque para que apareça em primeiro lugar
  10. +
  11. Clique em "Salvar"
  12. +
+
+ + Serviços inativos não aparecem no site público, mas permanecem salvos no sistema. +
+
+
+ + +
+
+
8. Perguntas Frequentes (FAQ)
+
+
+

No menu FAQ, você gerencia as perguntas frequentes exibidas no site.

+
Como cadastrar:
+
    +
  1. Clique em "Nova Pergunta"
  2. +
  3. Digite a pergunta
  4. +
  5. Digite a resposta
  6. +
  7. Defina a ordem de exibição (números menores aparecem primeiro)
  8. +
  9. Marque como ativa para que apareça no site
  10. +
  11. Clique em "Salvar"
  12. +
+
+ + Dica: Coloque as perguntas mais comuns com ordem menor (1, 2, 3...) para que apareçam primeiro. +
+
+
+ + +
+
+
9. Notícias
+
+
+

No menu Notícias, você gerencia artigos de notícias publicados no site.

+ +
Como criar uma notícia:
+
    +
  1. Clique em "Nova Notícia"
  2. +
  3. Preencha o título da notícia
  4. +
  5. O slug (endereço URL) é gerado automaticamente a partir do título, mas pode ser editado manualmente
  6. +
  7. Escreva um resumo breve que aparecerá na listagem de notícias
  8. +
  9. Escreva o conteúdo completo no editor
  10. +
  11. Defina o autor da notícia
  12. +
  13. Defina a data/hora de publicação
  14. +
  15. Faça upload da imagem principal (imagem de destaque)
  16. +
  17. Configure os campos SEO: +
      +
    • Título SEO: Título que aparece nos resultados do Google
    • +
    • Meta Description: Descrição que aparece nos resultados de busca
    • +
    • Palavras-chave: Termos relevantes para indexação
    • +
    +
  18. +
  19. Marque como ativa para publicar
  20. +
  21. Clique em "Salvar"
  22. +
+ +
Galeria de Imagens Adicionais
+

Após criar a notícia, você pode adicionar imagens adicionais na tela de edição:

+
    +
  1. Na edição da notícia, use a seção "Adicionar Imagens"
  2. +
  3. Selecione uma ou mais imagens
  4. +
  5. Adicione uma descrição/legenda para cada imagem
  6. +
  7. As imagens podem ser visualizadas em carrossel na página da notícia no site público
  8. +
+ +
+ + Dica: Notícias desativadas não aparecem no site, mas permanecem salvas. Use esse recurso para rascunhos. +
+
+
+ + +
+
+
10. Central de Downloads
+
+
+

No menu Downloads, você gerencia arquivos disponibilizados para os visitantes do site.

+
Como cadastrar:
+
    +
  1. Clique em "Novo Download"
  2. +
  3. Preencha o nome do arquivo
  4. +
  5. Adicione uma descrição
  6. +
  7. Selecione o tipo: Driver, Manual, Software ou Outro
  8. +
  9. Faça o upload do arquivo (até 20MB)
  10. +
  11. Defina se requer login para download
  12. +
  13. Marque como ativo
  14. +
  15. Clique em "Salvar"
  16. +
+
+
+ + +
+
+
11. Produtos
+
+
+

No menu Produtos, você gerencia o catálogo de produtos.

+ +
Categorias de Produtos
+

Antes de cadastrar produtos, crie as categorias:

+
    +
  1. Acesse Produtos > Categorias
  2. +
  3. Clique em "Nova Categoria"
  4. +
  5. Preencha nome e descrição
  6. +
  7. Clique em "Salvar"
  8. +
+ +
Cadastrando um Produto
+
    +
  1. Acesse Produtos > Novo Produto
  2. +
  3. Preencha nome, descrição, preço e categoria
  4. +
  5. Faça upload da imagem principal
  6. +
  7. Adicione imagens adicionais à galeria do produto
  8. +
  9. Marque como destaque se desejar
  10. +
  11. Ative o produto e clique em "Salvar"
  12. +
+
+
+ + +
+
+
12. Galerias de Imagens
+
+
+

No menu Galerias, você gerencia álbuns de fotos organizados por categorias.

+ +
Categorias de Galerias
+

As galerias são organizadas por categorias. No site público, o visitante navega pelas categorias no menu e visualiza as galerias de cada uma.

+
    +
  1. Acesse Galerias > Categorias
  2. +
  3. Clique em "Nova Categoria"
  4. +
  5. Preencha nome e descrição
  6. +
  7. Clique em "Salvar"
  8. +
+ +
Criando uma Galeria
+
    +
  1. Clique em "Nova Galeria"
  2. +
  3. Preencha o título da galeria
  4. +
  5. Adicione uma descrição
  6. +
  7. Selecione a categoria
  8. +
  9. Clique em "Salvar"
  10. +
+ +
Adicionando Imagens
+
    +
  1. Abra a galeria para edição
  2. +
  3. Na seção de upload, selecione uma ou mais imagens (até 5MB cada)
  4. +
  5. Após o upload, você pode adicionar legendas/descrições para cada imagem
  6. +
  7. Reordene as imagens conforme necessário
  8. +
+
+
+ + +
+
+
13. Páginas Institucionais
+
+
+

No menu Páginas, você gerencia as páginas institucionais do site (Sobre Nós, Missão, etc.).

+
Como criar uma página:
+
    +
  1. Clique em "Nova Página"
  2. +
  3. Preencha o título da página
  4. +
  5. O slug é gerado automaticamente (ex: /sobre-nos)
  6. +
  7. Escreva o conteúdo no editor visual
  8. +
  9. Configure os campos SEO: +
      +
    • Título SEO: Aparece na aba do navegador e nos resultados do Google
    • +
    • Meta Description: Descrição exibida nos resultados de busca
    • +
    +
  10. +
  11. Marque como ativa para publicar
  12. +
  13. Clique em "Salvar"
  14. +
+
+ + Dica: Páginas ativas aparecem automaticamente no menu de navegação do site público. +
+
+
+ + +
+
+
14. Marketing
+
+
+

A seção de Marketing oferece ferramentas para divulgar seu negócio e analisar visitantes.

+ +
Análise de Visitantes
+
    +
  • Gráfico de acessos: Visualize o tráfego dos últimos 7 dias
  • +
  • Geolocalização: Veja de quais países e cidades os visitantes acessam seu site
  • +
  • Páginas mais visitadas: Descubra quais conteúdos atraem mais visitantes
  • +
+ +
Banners da Home
+
    +
  • Gerencie os banners que aparecem na página inicial do site
  • +
  • Faça upload de imagens de alta qualidade (recomendado: 1920x600px)
  • +
  • Defina título, subtítulo e link de redirecionamento para cada banner
  • +
  • Controle a ordem e o status (ativo/inativo) de cada banner
  • +
+ +
Promover Produtos e Serviços
+
    +
  • Selecione um produto ou serviço específico
  • +
  • Obtenha links diretos para compartilhamento
  • +
  • Use os botões de WhatsApp, Facebook e LinkedIn para divulgar rapidamente
  • +
+ +
Email Marketing
+
    +
  • Redija um comunicado com assunto e corpo do e-mail
  • +
  • Vincule um produto ou serviço para inclusão automática de imagem e link
  • +
  • Ao enviar, todos os usuários cadastrados receberão a mensagem formatada
  • +
+
+
+ + +
+
+
15. Configurações do Sistema
+
+
+

No menu Dados da Empresa, você gerencia as configurações globais do site:

+ +
Dados da Empresa
+
    +
  • Nome da empresa: Exibido no cabeçalho e rodapé do site
  • +
  • CNPJ: Informação legal da empresa
  • +
  • Endereço: Exibido no rodapé e na página de contato
  • +
  • Telefone: Utilizado também para o botão flutuante do WhatsApp
  • +
  • E-mail: E-mail principal de contato
  • +
+ +
Redes Sociais
+
    +
  • Configure os links do Facebook, Instagram, LinkedIn e YouTube
  • +
  • Os ícones das redes sociais aparecem automaticamente no rodapé do site
  • +
+ +
WhatsApp
+
    +
  • Ativar/Desativar: Controle a exibição do botão flutuante
  • +
  • Horário de funcionamento: Defina horário de início, término e pausa para almoço
  • +
  • Mensagem padrão: Texto pré-preenchido quando o visitante clica no botão
  • +
+ +
SEO Global
+
    +
  • Título padrão: Título exibido nas abas do navegador
  • +
  • Palavras-chave: Termos para indexação global do site
  • +
+ +
Scripts de Rastreamento
+
    +
  • Insira códigos como Google Analytics ou Facebook Pixel
  • +
  • Os scripts são injetados automaticamente em todas as páginas
  • +
+ +
Modo Manutenção
+
    +
  • Ative para exibir uma página de "Em Manutenção" para visitantes
  • +
  • Enquanto ativo, administradores continuam tendo acesso ao painel
  • +
+
+
+ + +
+
+
16. Perfil e Preferências
+
+
+
Editar Perfil
+

Em Configurações > Perfil, você pode alterar:

+
    +
  • Nome completo
  • +
  • E-mail
  • +
  • Avatar (foto de perfil)
  • +
  • Senha de acesso
  • +
+ +
Preferências
+

Em Configurações > Preferências, você pode personalizar:

+
    +
  • Modo escuro: Ative/desative o tema escuro no painel administrativo
  • +
  • Notificações por e-mail: Controle o recebimento de notificações
  • +
+
+
+ + +
+
+
17. Notificações
+
+
+

O sistema envia notificações automáticas para os administradores. Elas aparecem no ícone de sino na barra superior.

+ +
Eventos que geram notificações:
+
    +
  • Novo usuário cadastrado: Quando alguém se registra no site
  • +
  • Nova mensagem de contato: Quando um visitante envia uma mensagem
  • +
  • Conteúdo criado/alterado: Quando páginas, galerias, notícias, produtos ou serviços são criados ou modificados
  • +
+ +
Gerenciando notificações:
+
    +
  • Clique no ícone de sino na barra superior para ver as notificações não lidas
  • +
  • Clique em uma notificação para ser redirecionado ao conteúdo relacionado
  • +
  • Clique em "Marcar todas como lidas" para limpar a lista
  • +
+
+
+ + +
+
+
18. Dicas e Boas Práticas
+
+
+
+
+
SEO
+
    +
  • Sempre preencha os campos de Título SEO e Meta Description em páginas e notícias
  • +
  • Use palavras-chave relevantes no conteúdo
  • +
  • Mantenha os slugs curtos e descritivos
  • +
  • Utilize imagens com textos alternativos
  • +
+ +
Imagens
+
    +
  • Use imagens otimizadas para web (formato WebP ou JPEG comprimido)
  • +
  • Banners: resolução recomendada de 1920x600px
  • +
  • Imagens de galeria: limite de 5MB por arquivo
  • +
  • Downloads: limite de 20MB por arquivo
  • +
+
+
+
Segurança
+
    +
  • Altere a senha padrão após o primeiro acesso
  • +
  • Use senhas fortes com letras, números e caracteres especiais
  • +
  • Revise periodicamente as permissões dos usuários
  • +
  • Desative usuários que não precisam mais de acesso
  • +
+ +
Manutenção
+
    +
  • Ative o Modo Manutenção antes de fazer grandes alterações
  • +
  • Revise periodicamente as mensagens de contato
  • +
  • Mantenha o FAQ atualizado para reduzir chamados de suporte
  • +
  • Verifique os dados da empresa periodicamente
  • +
+
+
+
+
+ + + + +
+@endsection diff --git a/resources/views/admin/marketing/analytics.blade.php b/resources/views/admin/marketing/analytics.blade.php new file mode 100755 index 0000000..425943c --- /dev/null +++ b/resources/views/admin/marketing/analytics.blade.php @@ -0,0 +1,136 @@ +@extends('layouts.admin') + +@section('title', 'Análise de Visitantes') + +@section('content') +
+

Análise de Visitantes

+ + Voltar + +
+ +
+ +
+
+
+
Acessos nos Últimos 7 Dias
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
Top 10 Países
+
+
+
+ + + + + + + + + @forelse($countries as $country) + + + + + @empty + + @endforelse + +
PaísAcessos
{{ $country->country }}{{ $country->total }}
Nenhum dado capturado ainda.
+
+
+
+
+ + +
+
+
+
Top 10 Cidades
+
+
+
+ + + + + + + + + @forelse($cities as $city) + + + + + @empty + + @endforelse + +
Cidade / RegiãoAcessos
{{ $city->city }} ({{ $city->region }}){{ $city->total }}
Nenhum dado capturado ainda.
+
+
+
+
+
+ +
+ Nota: A localização é estimada com base no endereço IP do visitante. A precisão pode variar dependendo do provedor de internet. Os dados são capturados apenas em páginas públicas (não administrativas). +
+@endsection + +@push('js') + + +@endpush diff --git a/resources/views/admin/marketing/email.blade.php b/resources/views/admin/marketing/email.blade.php new file mode 100755 index 0000000..417c6bb --- /dev/null +++ b/resources/views/admin/marketing/email.blade.php @@ -0,0 +1,142 @@ +@extends('layouts.admin') + +@section('title', 'E-mail Marketing') + +@section('content') +
+

Disparo de E-mail Marketing

+ + Voltar + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
+
+
Compor E-mail Promocional
+
+
+
+ @csrf + +
+ + + @error('subject')
{{ $message }}
@enderror +
+ +
+ + + @error('message')
{{ $message }}
@enderror +
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ +
+ +
+
+ + +
+
+ + +
+
+ A imagem principal do item selecionado será enviada no topo do e-mail. +
+ + +
+
+
+
+ +
+
+
+
Informações Importantes
+
+
+
+ Os e-mails serão enviados para todos os usuários com o tipo "User" que possuem a preferência de e-mail ativada. +
+
Dicas para um bom e-mail:
+
    +
  • Use um assunto curto e chamativo.
  • +
  • Vá direto ao ponto na mensagem.
  • +
  • Sempre inclua um botão de ação (CTA).
  • +
  • Evite e-mails muito longos com muitas imagens.
  • +
+
+

Total de destinatários aptos:
{{ $userCount }} clientes

+
+
+
+
+@endsection + +@push('css') + +@endpush + +@push('js') + + +@endpush diff --git a/resources/views/admin/marketing/index.blade.php b/resources/views/admin/marketing/index.blade.php new file mode 100755 index 0000000..6953af6 --- /dev/null +++ b/resources/views/admin/marketing/index.blade.php @@ -0,0 +1,182 @@ +@extends('layouts.admin') + +@section('title', 'Marketing & Promoção') + +@section('content') +
+

Marketing & Promoção

+
+ +
+ +
+
+
+
+
+
+ Destaques do Site
+
Gerenciar Banners
+

Crie banners rotativos para a página inicial para promover novidades e ofertas.

+ Acessar +
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ Vendas Rápidas
+
Promover Produtos
+

Gere links de compartilhamento para WhatsApp e Redes Sociais com mensagens prontas.

+ Acessar +
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ Atrair Clientes
+
Promover Serviços
+

Promova seus serviços especializados e atraia novas solicitações de orçamento.

+ Acessar +
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ Fidelização
+
E-mail Marketing
+

Envie promoções, novidades e ofertas personalizadas para todos os seus clientes por e-mail.

+ Configurar Disparo +
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ Geolocalização
+
Análise de Visitantes
+

Visualize de quais cidades e países as pessoas estão acessando o seu site em tempo real.

+ Ver Relatórios +
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ Visibilidade
+
Configurações de SEO
+

Ajuste as meta-tags globais do site para melhorar o ranqueamento no Google.

+ Configurar +
+
+ +
+
+
+
+
+
+ + +
+
+
+
+ +

SysCRM

+

Gestão de Relacionamento

+
+
+
+
+

Potencialize suas vendas com o SysCRM

+

Integre sua estratégia de marketing com a melhor ferramenta de gestão de clientes. O SysCRM permite que você acompanhe cada lead gerado pelo seu site, organize seu funil de vendas e nunca perca um negócio.

+
    +
  • Funil de Vendas Visual e Intuitivo
  • +
  • Gestão de Contatos e Histórico Completo
  • +
  • Automação de Tarefas e Lembretes
  • +
  • Integração Direta com seu Site
  • +
+ +
+
+
+
+ +
+
+
Dicas de Marketing
+
+
+
+
+
WhatsApp Marketing
+

Use a ferramenta de Promover Produtos para enviar ofertas personalizadas aos seus clientes. Uma mensagem com foto e link direto aumenta em 40% a chance de conversão.

+
+
+
Banners Estratégicos
+

Mantenha sempre pelo menos 2 banners ativos: um com um produto em destaque e outro com um serviço ou diferencial da sua empresa (ex: "Orçamento Grátis").

+
+
+
+
+@endsection diff --git a/resources/views/admin/marketing/promote_products.blade.php b/resources/views/admin/marketing/promote_products.blade.php new file mode 100755 index 0000000..9b29121 --- /dev/null +++ b/resources/views/admin/marketing/promote_products.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.admin') + +@section('title', 'Promover Produtos') + +@section('content') +
+

Promover Produtos

+ + Voltar + +
+ +
+
+
Ferramentas de Compartilhamento
+
+
+

Escolha um produto abaixo para gerar links de compartilhamento rápido em suas redes sociais e WhatsApp.

+ +
+ + + + + + + + + + + + @foreach($products as $product) + + + + + + + + @endforeach + +
FotoProdutoCategoriaPreçoLinks de Promoção
+ @if($product->image) + + @else +
+ +
+ @endif +
{{ $product->name }} + {{ $product->category->name ?? 'Sem Categoria' }} + R$ {{ number_format($product->price, 2, ',', '.') }} + @php + $productLink = route('produtos', ['categoria' => $product->category->slug ?? '']); + $productPrice = number_format($product->price, 2, ',', '.'); + + $rawMessage = \App\Models\Setting::get('marketing_product_text', 'Confira este produto incrível: {name} por apenas R$ {price}. Veja mais detalhes aqui: {link}'); + $shareMessage = str_replace( + ['{name}', '{price}', '{link}'], + [$product->name, $productPrice, $productLink], + $rawMessage + ); + @endphp + +
+
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/admin/marketing/promote_services.blade.php b/resources/views/admin/marketing/promote_services.blade.php new file mode 100755 index 0000000..78b26d2 --- /dev/null +++ b/resources/views/admin/marketing/promote_services.blade.php @@ -0,0 +1,107 @@ +@extends('layouts.admin') + +@section('title', 'Promover Serviços') + +@section('content') +
+

Promover Serviços

+ + Voltar + +
+ +
+
+
Ferramentas de Compartilhamento de Serviços
+
+
+

Escolha um serviço abaixo para gerar links de compartilhamento rápido e atrair novos orçamentos.

+ +
+ + + + + + + + + + @foreach($services as $service) + + + + + + @endforeach + +
ÍconeServiçoLinks de Promoção
+ @if($service->icon) + + @else + + @endif + {{ $service->name }} + @php + $serviceLink = route('servicos'); + + $rawMessage = \App\Models\Setting::get('marketing_service_text', 'Precisa de {name}? Nós podemos te ajudar! Confira nossos serviços aqui: {link}'); + $shareMessage = str_replace( + ['{name}', '{link}'], + [$service->name, $serviceLink], + $rawMessage + ); + @endphp + +
+
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/admin/news/create.blade.php b/resources/views/admin/news/create.blade.php new file mode 100755 index 0000000..8cb814c --- /dev/null +++ b/resources/views/admin/news/create.blade.php @@ -0,0 +1,111 @@ +@extends('layouts.admin') + +@section('title', 'Nova Notícia') + +@section('content') +
+

Criar Notícia

+ + Voltar + +
+ +
+
+
Informações da Notícia
+
+
+
+ @csrf + +
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('slug')
{{ $message }}
@enderror +
+ +
+
+
+ + + @error('author')
{{ $message }}
@enderror +
+
+
+
+ + + @error('published_at')
{{ $message }}
@enderror + Deixe vazio para publicar imediatamente. +
+
+
+ +
+ + + @error('featured_image')
{{ $message }}
@enderror + Imagem de destaque da notícia (máx. 5MB). +
+ +
+ + + @error('summary')
{{ $message }}
@enderror + Breve descrição exibida na listagem (máx. 500 caracteres). +
+ +
+ + + @error('content')
{{ $message }}
@enderror +
+ +
+
SEO (Opcional)
+ +
+ + + @error('seo_title')
{{ $message }}
@enderror +
+ +
+ + + @error('seo_description')
{{ $message }}
@enderror +
+ +
+ + + @error('seo_keywords')
{{ $message }}
@enderror +
+ +
+ +
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/admin/news/edit.blade.php b/resources/views/admin/news/edit.blade.php new file mode 100755 index 0000000..2cc62d5 --- /dev/null +++ b/resources/views/admin/news/edit.blade.php @@ -0,0 +1,168 @@ +@extends('layouts.admin') + +@section('title', 'Editar Notícia') + +@section('content') +
+

Editar Notícia: {{ $news->title }}

+ + Voltar + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+ +
+
+
+
Configurações
+
+
+
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Vazio = publicação imediata. +
+ +
+ + +
+ +
+ + @if($news->featured_image) +
+ Featured +
+ + Remover Imagem + + @endif + + Envie uma nova imagem para substituir a atual. +
+ +
+ + +
+ +
+ + +
+ +
+
SEO
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ + +
+
+
Adicionar Imagens
+
+
+
+ @csrf +
+ + + Selecione uma ou mais imagens (máx. 5MB cada). +
+ +
+
+
+
+ + +
+
+
+
Imagens da Notícia
+
+
+
+ @forelse($news->images as $img) +
+
+ +
+
+ @csrf + @method('PUT') +
+ +
+
+ + +
+
+
+ @csrf + @method('DELETE') + +
+
+
+
+ @empty +
+

Nenhuma imagem adicional adicionada.

+
+ @endforelse +
+
+
+
+
+@endsection diff --git a/resources/views/admin/news/index.blade.php b/resources/views/admin/news/index.blade.php new file mode 100755 index 0000000..0891c5f --- /dev/null +++ b/resources/views/admin/news/index.blade.php @@ -0,0 +1,101 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Notícias') + +@section('content') +
+

+ Notícias + + {{ $newsEnabled ? 'Ativo' : 'Inativo' }} + +

+
+
+ @csrf + +
+ + Nova Notícia + +
+
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Notícias
+
+
+
+ + + + + + + + + + + + + + @foreach($newsList as $item) + + + + + + + + + + @endforeach + +
ImagemTítuloAutorPublicaçãoImagensStatusAções
+ @if($item->featured_image) + {{ $item->title }} + @else +
+ +
+ @endif +
{{ $item->title }}{{ $item->author ?? '—' }} + @if($item->published_at) + {{ $item->published_at->format('d/m/Y H:i') }} + @else + — + @endif + {{ $item->images_count }} imagens + + {{ $item->active ? 'Ativa' : 'Inativa' }} + + + + Editar + + + Ver + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $newsList->links() }} +
+
+
+@endsection diff --git a/resources/views/admin/pages/create.blade.php b/resources/views/admin/pages/create.blade.php new file mode 100755 index 0000000..1297e75 --- /dev/null +++ b/resources/views/admin/pages/create.blade.php @@ -0,0 +1,116 @@ +@extends('layouts.admin') + +@section('title', 'Nova Página') + +@section('content') +
+

Nova Página

+ + Voltar + +
+ +
+
+
Criar Conteúdo
+
+
+
+ @csrf + +
+
+
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('content')
{{ $message }}
@enderror +
+
+ +
+
+ + + Ex: minha-nova-pagina + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('meta_description')
{{ $message }}
@enderror +
+ +
+
SEO Adicional
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + + +@endpush + +@push('js') + + +@endpush diff --git a/resources/views/admin/pages/edit.blade.php b/resources/views/admin/pages/edit.blade.php new file mode 100755 index 0000000..d2de717 --- /dev/null +++ b/resources/views/admin/pages/edit.blade.php @@ -0,0 +1,176 @@ +@extends('layouts.admin') + +@section('title', 'Editar Página') + +@section('content') +@php + $managedPages = [ + 'contato' => ['label' => 'Contato', 'route' => route('admin.contacts.index'), 'section' => 'Mensagens de Contato'], + 'faq' => ['label' => 'FAQ', 'route' => route('admin.faqs.index'), 'section' => 'Gerenciamento de FAQ'], + 'downloads' => ['label' => 'Downloads', 'route' => route('admin.downloads.index'), 'section' => 'Gerenciamento de Downloads'], + 'produtos' => ['label' => 'Produtos', 'route' => '#', 'section' => 'Módulo de Produtos'], + 'servicos' => ['label' => 'Serviços', 'route' => '#', 'section' => 'Módulo de Serviços'], + ]; + $isManaged = isset($managedPages[$page->slug]); +@endphp + +
+

Editar Página: {{ $page->title }}

+ + Voltar + +
+ +@if($isManaged) +
+
+ +
+
Página com conteúdo dinâmico
+

+ O conteúdo desta página ({{ $managedPages[$page->slug]['label'] }}) é gerado dinamicamente pelo sistema e não deve ser editado manualmente por aqui. As informações exibidas nela são controladas pela seção {{ $managedPages[$page->slug]['section'] }} no painel administrativo. +

+ @if($managedPages[$page->slug]['route'] !== '#') + + Ir para {{ $managedPages[$page->slug]['section'] }} + + @endif +

+ Você ainda pode ajustar o título, slug, SEO e o status da página abaixo, mas o conteúdo principal será sobrescrito pelo módulo correspondente. +

+
+
+
+@endif + +
+
+
Editar Conteúdo
+ + Visualizar no Site + +
+
+
+ @csrf + @method('PUT') + +
+
+
+ + + @error('title')
{{ $message }}
@enderror +
+ +
+ + + @error('content')
{{ $message }}
@enderror +
+
+ +
+
+ + slug, ['index', 'about', 'produtos', 'servicos', 'downloads', 'contato', 'faq']) ? 'readonly' : 'required' }}> + As páginas principais do sistema não podem ter o slug alterado. + @error('slug')
{{ $message }}
@enderror +
+ +
+ + + @error('meta_description')
{{ $message }}
@enderror +
+ +
+ + +
+ +
+
Configurações Avançadas de SEO
+ +
+ + +
+ +
+ + +
+ +
+
Redes Sociais (Open Graph)
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+
+@endsection + +@push('css') + + +@endpush + +@push('js') + + +@endpush diff --git a/resources/views/admin/pages/index.blade.php b/resources/views/admin/pages/index.blade.php new file mode 100755 index 0000000..ac33563 --- /dev/null +++ b/resources/views/admin/pages/index.blade.php @@ -0,0 +1,75 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Páginas') + +@section('content') +
+

Páginas Institucionais

+ + Nova Página + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+
+
Lista de Páginas
+
+
+
+ + + + + + + + + + + + @foreach($pages as $page) + @php + $managedSlugs = ['contato', 'faq', 'downloads', 'produtos', 'servicos']; + $isManaged = in_array($page->slug, $managedSlugs); + @endphp + + + + + + + + @endforeach + +
TítuloSlug (URL)StatusÚltima AtualizaçãoAções
+ {{ $page->title }} + @if($isManaged) + Sistema + @endif + /{{ $page->slug }} + + {{ $page->active ? 'Ativa' : 'Inativa' }} + + {{ $page->updated_at->format('d/m/Y H:i') }} + + Editar + + + Ver + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/settings/index.blade.php b/resources/views/admin/settings/index.blade.php new file mode 100755 index 0000000..30db6a8 --- /dev/null +++ b/resources/views/admin/settings/index.blade.php @@ -0,0 +1,207 @@ +@extends('layouts.admin') + +@section('title', 'Configurações do Site') + +@section('content') +
+

Configurações Gerais

+
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+ @csrf + + + + + +
+ +
+
+
+
+
+
Informações Institucionais
+
+
+
+
+
+ + +
+
+ + + Aparece no rodapé e em outros locais +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
Redes Sociais (Links completos)
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
Mapa de Localização
+
+ + + Vá no Google Maps, compartilhe o local e selecione "Incorporar mapa" para obter o código iframe. Aparece na página de Contato. +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
Configurações de Buscadores
+
+
+
+ + + Ex: "Início - Syspanel" +
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
Integrações e Código
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
Textos de Divulgação (WhatsApp / Redes Sociais)
+
+
+
+
+
+ + + Use as tags: {name} para o nome, {price} para o preço e {link} para a URL do produto. +
+
+
+
+ + + Use as tags: {name} para o nome e {link} para a URL da página de serviços. +
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+@endsection diff --git a/resources/views/admin/subjects/create.blade.php b/resources/views/admin/subjects/create.blade.php new file mode 100755 index 0000000..5662516 --- /dev/null +++ b/resources/views/admin/subjects/create.blade.php @@ -0,0 +1,36 @@ +@extends('layouts.admin') + +@section('title', 'Novo Assunto') + +@section('content') +
+

Novo Assunto

+ + Voltar + +
+ +
+
+
+
+
Criar Assunto para Contato
+
+
+
+ @csrf +
+ + + @error('name')
{{ $message }}
@enderror +
+ + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/subjects/edit.blade.php b/resources/views/admin/subjects/edit.blade.php new file mode 100755 index 0000000..6078765 --- /dev/null +++ b/resources/views/admin/subjects/edit.blade.php @@ -0,0 +1,46 @@ +@extends('layouts.admin') + +@section('title', 'Editar Assunto') + +@section('content') +
+

Editar Assunto

+ + Voltar + +
+ +
+
+
+
+
Assunto: {{ $subject->name }}
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + +
+ + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/subjects/index.blade.php b/resources/views/admin/subjects/index.blade.php new file mode 100755 index 0000000..5db0eee --- /dev/null +++ b/resources/views/admin/subjects/index.blade.php @@ -0,0 +1,65 @@ +@extends('layouts.admin') + +@section('title', 'Assuntos de Contato') + +@section('content') +
+

Assuntos

+ + Novo Assunto + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +@if(session('error')) +
{{ session('error') }}
+@endif + +
+
+
Lista de Assuntos
+
+
+
+ + + + + + + + + + + @foreach($subjects as $subject) + + + + + + + @endforeach + +
NomeStatusMensagensAções
{{ $subject->name }} + + {{ $subject->active ? 'Ativo' : 'Inativo' }} + + {{ $subject->messages_count ?? $subject->messages()->count() }} + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/user-types/create.blade.php b/resources/views/admin/user-types/create.blade.php new file mode 100755 index 0000000..b507ee9 --- /dev/null +++ b/resources/views/admin/user-types/create.blade.php @@ -0,0 +1,52 @@ +@extends('layouts.admin') + +@section('title', 'Novo Tipo de Usuário') + +@section('content') +
+

Novo Tipo de Usuário

+ + Voltar + +
+ +
+
+
+
+
Criar Tipo de Usuário
+
+
+
+ @csrf + +
+ + + @error('name')
{{ $message }}
@enderror + Este nome será usado como identificador do tipo de usuário. +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+ +
+
+ + + Quando ativado, usuários deste tipo terão acesso total ao sistema (todas as permissões). +
+
+ + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/user-types/edit.blade.php b/resources/views/admin/user-types/edit.blade.php new file mode 100755 index 0000000..ad4b094 --- /dev/null +++ b/resources/views/admin/user-types/edit.blade.php @@ -0,0 +1,57 @@ +@extends('layouts.admin') + +@section('title', 'Editar Tipo de Usuário') + +@section('content') +
+

Editar Tipo: {{ $userType->name }}

+ + Voltar + +
+ +
+
+
+
+
Editar Tipo de Usuário
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('name')
{{ $message }}
@enderror + @if($userType->is_protected) + Este é um tipo protegido. Alterar o nome afetará todos os usuários deste tipo. + @else + Alterar o nome atualizará automaticamente todos os usuários deste tipo. + @endif +
+ +
+ + + @error('description')
{{ $message }}
@enderror +
+ +
+
+ is_admin) ? 'checked' : '' }}> + + Quando ativado, usuários deste tipo terão acesso total ao sistema (todas as permissões). +
+
+ + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/user-types/index.blade.php b/resources/views/admin/user-types/index.blade.php new file mode 100755 index 0000000..d39460a --- /dev/null +++ b/resources/views/admin/user-types/index.blade.php @@ -0,0 +1,83 @@ +@extends('layouts.admin') + +@section('title', 'Tipos de Usuário') + +@section('content') +
+

Tipos de Usuário

+ + Novo Tipo + +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +@if(session('error')) +
{{ session('error') }}
+@endif + +
+
+
Lista de Tipos de Usuário
+
+
+
+ + + + + + + + + + + + + @foreach($userTypes as $type) + + + + + + + + + @endforeach + +
NomeDescriçãoAdministradorProtegidoUsuáriosAções
+ {{ $type->name }} + {{ $type->description ?? '-' }} + @if($type->is_admin) + Sim + @else + Não + @endif + + @if($type->is_protected) + Sim + @else + Não + @endif + + {{ $type->users_count }} + + + + + @unless($type->is_protected) +
+ @csrf + @method('DELETE') + +
+ @endunless +
+
+
+
+@endsection diff --git a/resources/views/admin/users/create.blade.php b/resources/views/admin/users/create.blade.php new file mode 100755 index 0000000..a243e59 --- /dev/null +++ b/resources/views/admin/users/create.blade.php @@ -0,0 +1,138 @@ +@extends('layouts.admin') + +@section('title', 'Novo Usuário') + +@section('content') +
+

Novo Usuário

+ + Voltar + +
+ +
+
+
+
+
Criar Novo Cadastro
+
+
+
+ @csrf + +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('email')
{{ $message }}
@enderror +
+ +
+ +
+ +
+ +
+ @error('password')
{{ $message }}
@enderror +
+
+ +
+
+ + Gerenciar Tipos +
+ + @error('user_type')
{{ $message }}
@enderror + Tipos marcados como Admin têm acesso total ao sistema. Os demais têm acesso apenas às seções permitidas abaixo. +
+ +
+ + + @error('company')
{{ $message }}
@enderror +
+ +
+
+ + + @error('website')
{{ $message }}
@enderror +
+
+ + + @error('whatsapp')
{{ $message }}
@enderror +
+
+ + +
+
+
Permissões de Acesso
+

Selecione quais seções do sistema este usuário poderá acessar. Esta configuração se aplica apenas a usuários do tipo User.

+ +
+ @foreach($availablePermissions as $key => $label) +
+
+ + +
+
+ @endforeach +
+ +
+ + +
+
+ + +
+
+
+
+
+ +@push('js') + +@endpush +@endsection diff --git a/resources/views/admin/users/edit.blade.php b/resources/views/admin/users/edit.blade.php new file mode 100755 index 0000000..442af94 --- /dev/null +++ b/resources/views/admin/users/edit.blade.php @@ -0,0 +1,168 @@ +@extends('layouts.admin') + +@section('title', 'Editar Usuário') + +@section('content') +
+

Editar Usuário: {{ $user->name }}

+ + Voltar + +
+ +
+
+
+
+
Informações de {{ $user->name }}
+
+
+
+ @csrf + @method('PUT') + +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + +
+ +
+ +
+ + +
+ @if(!$user->email_verified_at) + Este usuário ainda não confirmou o e-mail. + @endif +
+ +
+
+ + Gerenciar Tipos +
+ + @error('user_type')
{{ $message }}
@enderror + Tipos marcados como Admin têm acesso total ao sistema. Os demais têm acesso apenas às seções permitidas abaixo. +
+ +
+ + + @error('company')
{{ $message }}
@enderror +
+ +
+
+ + + @error('website')
{{ $message }}
@enderror +
+
+ + + @error('whatsapp')
{{ $message }}
@enderror +
+
+ +
+
Alterar Senha (Opcional)
+

Preencha os campos abaixo apenas se desejar alterar a senha deste usuário. Caso contrário, deixe-os em branco.

+ +
+
+ +
+ +
+ +
+ @error('password')
{{ $message }}
@enderror +
+
+
+ +
+ +
+ +
+
+
+
+ + +
+
+
Permissões de Acesso
+

Selecione quais seções do sistema este usuário poderá acessar. Esta configuração se aplica apenas a usuários do tipo User.

+ +
+ @foreach($availablePermissions as $key => $label) +
+
+ permissions ?? [])) ? 'checked' : '' }}> + +
+
+ @endforeach +
+ +
+ + +
+
+ + +
+
+
+
+
+ +@push('js') + +@endpush +@endsection diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php new file mode 100755 index 0000000..89c2253 --- /dev/null +++ b/resources/views/admin/users/index.blade.php @@ -0,0 +1,100 @@ +@extends('layouts.admin') + +@section('title', 'Gerenciar Cadastros') + +@section('content') +
+

Administração de Usuários

+ +
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +@if(session('error')) +
{{ session('error') }}
+@endif + +
+
+
Lista de Usuários
+
+
+
+ + + + + + + + + + + + + + @foreach($users as $user) + + + + + + + + + + @endforeach + +
NomeE-mailTipoPermissõesEmpresaWhatsAppAções
{{ $user->name }}{{ $user->email }} + @php + $typeIsAdmin = false; + foreach($userTypes as $ut) { + if ($ut->name === $user->user_type && $ut->is_admin) { + $typeIsAdmin = true; + break; + } + } + @endphp + + {{ $user->user_type }} + + + @if($user->isAdmin()) + Acesso Total + @elseif(is_array($user->permissions) && count($user->permissions) > 0) + @foreach($user->permissions as $perm) + @if(isset($availablePermissions[$perm])) + {{ $availablePermissions[$perm] }} + @endif + @endforeach + @else + Nenhuma permissão + @endif + {{ $user->company ?? '-' }}{{ $user->whatsapp ?? '-' }} + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+ {{ $users->links() }} +
+
+
+@endsection diff --git a/resources/views/assistentes-digitais-ia.blade.php b/resources/views/assistentes-digitais-ia.blade.php new file mode 100755 index 0000000..2232ede --- /dev/null +++ b/resources/views/assistentes-digitais-ia.blade.php @@ -0,0 +1,178 @@ +@extends('layouts.app') + +@section('title', $page->title ?? 'Assistentes Digitais com IA - Syspanel') +@section('meta_description', $page->meta_description ?? 'Entenda o que são Assistentes Digitais com IA e suas utilidades práticas para o dia a dia.') + +@section('content') + + + +
+
+
+
+ TECNOLOGIA DE PONTA +

Assistentes Digitais de Alta Performance

+

+ Entenda como a Inteligência Artificial transformou simples chatbots em agentes cognitivos capazes de entender, aprender e agir de forma autônoma. +

+ +
+
+ +
+
+
+
+ + +
+
+
+
+

O que é um Assistente Digital?

+
+

+ Muito além de um simples robô de respostas, os assistentes digitais modernos utilizam processamento de linguagem natural para interagir de forma inteligente: +

+
    +
  • + +
    Compreensão Real: Entendem o contexto e a intenção por trás das palavras, não apenas palavras-chave isoladas.
    +
  • +
  • + +
    Aprendizado Contínuo: Evoluem a cada conversa, tornando-se mais precisos e inteligentes com o tempo.
    +
  • +
  • + +
    Integração de Dados: Conectam-se a bases de conhecimento para fornecer informações exatas em milissegundos.
    +
  • +
  • + +
    Execução de Tarefas: Podem agir de forma autônoma para realizar agendamentos e triagens.
    +
  • +
+
+
+
+
+
+
+ +
+

Cognição

+

Arquitetura inspirada no processamento humano para resolver problemas complexos.

+
+
+
+
+
+ +
+

Agilidade

+

Respostas imediatas e precisas que eliminam filas de espera e aumentam a satisfação.

+
+
+
+
+
+
+
+ + +
+
+
+
+

Pronto para o Próximo Nível?

+

A inteligência artificial é a ferramenta indispensável para quem busca escala hoje.

+
+
+
+
Arquitetura Personalizada
+

Implementações que respeitam sua identidade e regras de negócio.

+
+
+
+
+
+
Privacidade e Segurança
+

Processamento seguro de informações em total conformidade.

+
+
+
+
+
+
Evolução Contínua
+

Acompanhamento constante para refinamento da base de conhecimento.

+
+
+
+
+
+

Solicite um Diagnóstico

+

Nossa equipe analisará seus processos atuais e proporemos a melhor solução de IA.

+ Falar com Especialista +
+
+
+
+
+ +@endsection diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100755 index 0000000..0ca39b4 --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,88 @@ +@extends('layouts.app') + +@section('title', 'Login - Syspanel') + +@section('content') +
+
+
+
+
+
+ +

Bem-vindo de volta!

+

Acesse sua conta para gerenciar seus serviços.

+
+
+ @if(session('error')) +
+ {{ session('error') }} +
+ @endif +
+ @csrf +
+ +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+
+ + @if (Route::has('password.request')) + Esqueceu a senha? + @endif +
+
+ + + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + @include('partials.captcha') + + + + @if(Route::has('register') && \App\Models\Setting::get('public_registration') == 'on') +
+ Não tem uma conta? + Cadastre-se +
+ @endif +
+
+
+
+ + +
+
+
+@endsection diff --git a/resources/views/auth/passwords/confirm.blade.php b/resources/views/auth/passwords/confirm.blade.php new file mode 100755 index 0000000..2d0b7ff --- /dev/null +++ b/resources/views/auth/passwords/confirm.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Confirm Password') }}
+ +
+ {{ __('Please confirm your password before continuing.') }} + +
+ @csrf + +
+ + +
+
+ + +
+ + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+
+ + + @if (Route::has('password.request')) + + {{ __('Forgot Your Password?') }} + + @endif +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php new file mode 100755 index 0000000..1da0c3e --- /dev/null +++ b/resources/views/auth/passwords/email.blade.php @@ -0,0 +1,53 @@ +@extends('layouts.app') + +@section('title', 'Recuperar Senha - Syspanel') + +@section('content') +
+
+
+
+
+ +

Recuperar Senha

+

Enviaremos um link para você redefinir sua senha.

+
+
+ @if (session('status')) + + @endif + +
+ @csrf + +
+ +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ + @include('partials.captcha') + + + + +
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php new file mode 100755 index 0000000..a27cbba --- /dev/null +++ b/resources/views/auth/passwords/reset.blade.php @@ -0,0 +1,72 @@ +@extends('layouts.app') + +@section('title', 'Redefinir Senha - Syspanel') + +@section('content') +
+
+
+
+
+ +

Redefinir Senha

+

Crie uma nova senha para sua conta.

+
+
+
+ @csrf + + + +
+ +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ +
+ + + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ +
+ + + +
+
+ + @include('partials.captcha') + + +
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100755 index 0000000..e857aac --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,152 @@ +@extends('layouts.app') + +@section('title', 'Cadastro - Syspanel') + +@section('content') +
+
+
+
+
+
+ +

Crie sua conta

+

Junte-se à Syspanel e gerencie seus serviços.

+
+
+
+ @csrf + +
+ +
+ + + @error('name') + + {{ $message }} + + @enderror +
+
+ +
+ +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ +
+ + + @error('user_type') + + {{ $message }} + + @enderror +
+
+ +
+ +
+ + + @error('company') + + {{ $message }} + + @enderror +
+
+ +
+
+ +
+ + + @error('website') + + {{ $message }} + + @enderror +
+
+
+ +
+ + + @error('whatsapp') + + {{ $message }} + + @enderror +
+
+
+ +
+
+ +
+ + + + @error('password') + + {{ $message }} + + @enderror +
+
+
+ +
+ + + +
+
+
+ +
+ Ao se cadastrar, você concorda com nossos Termos de Uso. +
+ + @include('partials.captcha') + + + +
+ Já tem uma conta? + Faça login +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/verify.blade.php b/resources/views/auth/verify.blade.php new file mode 100755 index 0000000..e5993ff --- /dev/null +++ b/resources/views/auth/verify.blade.php @@ -0,0 +1,38 @@ +@extends('layouts.app') + +@section('title', 'Verificar E-mail - Syspanel') + +@section('content') +
+
+
+
+
+ +

Verifique seu E-mail

+

Enviamos um link de confirmação para o seu endereço de e-mail.

+
+
+ @if (session('resent')) + + @endif + +

+ Antes de prosseguir, por favor verifique seu e-mail em busca do link de confirmação. + Se você não recebeu o e-mail, clique no botão abaixo para solicitar outro. +

+ +
+ @csrf + +
+
+
+
+
+
+@endsection diff --git a/resources/views/contato.blade.php b/resources/views/contato.blade.php new file mode 100755 index 0000000..4fa239d --- /dev/null +++ b/resources/views/contato.blade.php @@ -0,0 +1,218 @@ +@extends('layouts.app') + +@section('title', $page ? $page->title : 'Contato') +@section('meta_description', $page ? $page->meta_description : 'Entre em contato com a Syspanel.') +@section('meta_keywords', $page ? $page->meta_keywords : '') +@section('og_title', $page ? $page->og_title : '') +@section('og_description', $page ? $page->og_description : '') +@section('og_image', $page ? $page->og_image : '') +@section('canonical', $page ? $page->canonical_url : '') + +@section('content') + + + +
+
+

Fale Conosco

+ +
+ +
+
+

Informações de Contato

+

Entre em contato conosco para tirar dúvidas, solicitar orçamentos ou agendar uma visita técnica.

+ +
+ +
+
Localização
+

{{ $company->address ?? 'Endereço não informado' }}

+
+
+ +
+ +
+
Telefone e WhatsApp
+

{{ $company->phone ?? 'Não informado' }}

+
+
+ +
+ +
+
E-mail
+

{{ $company->email ?? 'Não informado' }}

+
+
+ +
+ +
+
Horário de Atendimento
+

{{ $company->hours ?? 'Não informado' }}

+
+
+ +
+ +
Redes Sociais
+
+ @if($company->facebook) + + @endif + @if($company->instagram) + + @endif + @if($company->linkedin) + + @endif + @if($company->phone) + + @endif +
+
+
+ + +
+
+

Envie uma Mensagem

+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + +
+ @csrf +
+
+
+ + + @error('name')
{{ $message }}
@enderror +
+
+
+
+ + + @error('email')
{{ $message }}
@enderror +
+
+
+
+ + + @error('company')
{{ $message }}
@enderror +
+
+
+
+ + + @error('website')
{{ $message }}
@enderror +
+
+
+
+ + + @error('whatsapp')
{{ $message }}
@enderror +
+
+
+
+ + + @error('subject_id')
{{ $message }}
@enderror +
+
+
+
+ + + @error('message')
{{ $message }}
@enderror +
+
+ + +
+
+ +
+ @php + $n1 = rand(1, 9); + $n2 = rand(1, 9); + session(['captcha_result' => $n1 + $n2]); + @endphp +
+ {{ $n1 }} + {{ $n2 }} = ? +
+
+ + @error('captcha')
{{ $message }}
@enderror +
+
+ Por favor, resolva a conta acima para provar que você não é um robô. +
+
+ +
+ +
+
+
+
+
+
+
+
+ + @php + $mapIframe = \App\Models\Setting::get('company_map_iframe'); + // Sanitize iframe: only allow iframes from trusted domains + $allowedDomains = ['google.com', 'maps.google.com', 'www.google.com', 'openstreetmap.org', 'www.openstreetmap.org']; + $safeIframe = null; + + if ($mapIframe && preg_match('/]*src=["\']https?:\/\/([^"\']+)["\'][^>]*>/i', $mapIframe, $matches)) { + $iframeDomain = parse_url('https://' . $matches[1], PHP_URL_HOST); + $isTrusted = false; + + foreach ($allowedDomains as $allowed) { + if (str_ends_with($iframeDomain, $allowed)) { + $isTrusted = true; + break; + } + } + + if ($isTrusted) { + $safeIframe = $mapIframe; + } + } + @endphp + @if($safeIframe) +
+
+
+ {!! $safeIframe !!} +
+
+
+ @endif +@endsection diff --git a/resources/views/downloads.blade.php b/resources/views/downloads.blade.php new file mode 100755 index 0000000..c57bca0 --- /dev/null +++ b/resources/views/downloads.blade.php @@ -0,0 +1,88 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') +
+
+

Downloads e Utilitários

+

Arquivos exclusivos para nossos clientes cadastrados.

+
+ +
+
+ @guest +
+
+ +
+

Acesso Restrito

+

+ Para garantir a segurança e integridade dos arquivos, a área de downloads é exclusiva para usuários cadastrados. +

+ +
+ @else +
+
+ + + + + + + + + + + @forelse($downloads as $download) + + + + + + + @empty + + + + @endforelse + +
ArquivoTipoDescriçãoAção
+
{{ $download->title }}
+ Postado em {{ $download->created_at->format('d/m/Y') }} +
+ + {{ $download->file_type }} + + + {{ $download->description ?? 'Sem descrição disponível.' }} + + + Baixar + +
+ + Nenhum arquivo disponível para download no momento. +
+
+
+ +
+ + Dica: Se não encontrar o driver específico para seu equipamento, entre em contato com nosso suporte técnico. +
+ @endguest +
+
+
+@endsection diff --git a/resources/views/emails/contact-reply.blade.php b/resources/views/emails/contact-reply.blade.php new file mode 100755 index 0000000..82e6f06 --- /dev/null +++ b/resources/views/emails/contact-reply.blade.php @@ -0,0 +1,35 @@ + + + + + + + +
+
+

Resposta ao seu Contato

+
+
+

Olá, {{ $contact->name }},

+ +

{{ $replyMessage }}

+ +
+

Sua mensagem original:

+

{{ $contact->message }}

+
+
+ +
+ + diff --git a/resources/views/emails/promotion.blade.php b/resources/views/emails/promotion.blade.php new file mode 100755 index 0000000..d85cd7a --- /dev/null +++ b/resources/views/emails/promotion.blade.php @@ -0,0 +1,18 @@ + +# {{ $subjectLine }} + +@if($productImage) +Promoção +@endif + +{!! $messageBody !!} + +@if($buttonUrl) + +{{ $buttonText ?: 'Confira agora' }} + +@endif + +Obrigado,
+Equipe {{ $company->name ?? config('app.name') }} +
diff --git a/resources/views/erp.blade.php b/resources/views/erp.blade.php new file mode 100755 index 0000000..9a353fc --- /dev/null +++ b/resources/views/erp.blade.php @@ -0,0 +1,313 @@ +@extends('layouts.app') + +@section('title', $page->title ?? 'ERP Syspanel - Gestão Inteligente') +@section('meta_description', $page->meta_description ?? 'Descubra como o ERP Syspanel pode transformar a gestão da sua empresa com módulos personalizados e inteligência artificial.') +@section('meta_keywords', $page->meta_keywords ?? 'ERP, Sistema de Gestão, Gestão Empresarial, Automação, Syspanel, Financeiro, CRM') +@section('og_title', $page->og_title ?? 'ERP Syspanel - Gestão Inteligente') +@section('og_description', $page->og_description ?? 'A solução completa para a gestão total do seu negócio.') +@section('og_image', $page->og_image ?? '') +@section('canonical', $page->canonical_url ?? '') + +@section('content') + + + +
+
+
+
+ SISTEMA DE GESTÃO COMPLETO +

ERP Syspanel: Inteligência em Cada Processo

+

+ A solução definitiva para integrar todos os setores da sua empresa. Do financeiro à produção, com módulos personalizados que crescem com o seu negócio. +

+ +
+
+ +
+
+
+
+ + +
+
+
+
+

O que é um ERP?

+

+ ERP (Enterprise Resource Planning) é um sistema que centraliza todas as informações e processos de uma empresa em uma única plataforma. Imagine ter financeiro, vendas, estoque, RH e produção conversando entre si em tempo real. +

+

+ O ERP Syspanel vai além: ele utiliza Inteligência Artificial para analisar seus dados e sugerir melhorias, tornando sua gestão não apenas organizada, mas estratégica. +

+
+
+
+
+
+
+
Integração
+
+
+
+
+
+
Agilidade
+
+
+
+
+
+
Segurança
+
+
+
+
+
+
Inteligência
+
+
+
+
+
+
+
+ + +
+
+
+

Vantagens do Ecossistema

+

Como o ERP Syspanel beneficia todos os envolvidos no seu negócio

+
+ +
+ +
+
+
Para o Empresário
+
+
    +
  • Visão 360º do negócio em tempo real.
  • +
  • Redução drástica de erros e retrabalho.
  • +
  • Decisões seguras baseadas em indicadores (KPIs).
  • +
+
+
+
+ +
+
+
Para os Funcionários
+
+
    +
  • Automação de tarefas repetitivas.
  • +
  • Facilidade no acesso a informações necessárias.
  • +
  • Ambiente de trabalho mais organizado e produtivo.
  • +
+
+
+
+ +
+
+
Para a Empresa
+
+
    +
  • Processos padronizados e auditáveis.
  • +
  • Melhoria contínua da saúde financeira.
  • +
  • Crescimento sustentável e escalável.
  • +
+
+
+
+ +
+
+
Para os Clientes
+
+
    +
  • Atendimento mais rápido e preciso.
  • +
  • Transparência em pedidos e entregas.
  • +
  • Melhor experiência de compra/serviço.
  • +
+
+
+
+ +
+
+
Para os Fornecedores
+
+
    +
  • Previsibilidade de demanda e pedidos.
  • +
  • Gestão eficiente de recebimentos.
  • +
  • Parceria baseada em dados reais.
  • +
+
+
+
+ +
+
+
ERP em Nuvem
+
+
    +
  • Sem Custos de Hardware: Não necessita de servidores próprios ou TI local.
  • +
  • Acesso Global: Trabalhe de qualquer lugar, a qualquer hora, com segurança total.
  • +
  • Atualizações Automáticas: Seu sistema sempre na última versão, sem paradas.
  • +
+
+
+
+
+
+
+ + +
+
+
+
+

Módulos que Evoluem com Você

+

Nosso sistema é modular. Você começa com o que precisa e expande conforme sua necessidade.

+
+
+ +
+
+
+ Financeiro + Fluxo de Caixa + CRM + Vendas + Compras + Gestão de Projetos + Plano de Negócios + Estoque + Produção + Produção (PCP) + Frotas + Controle de Veículos + RH + IA ERP + Qualidade + Outros +
+
+
+ +
+
+
+
+ +
+

Necessidade Única? Módulo Novo!

+

+ Sua empresa tem um processo exclusivo? Nós desenvolvemos e acrescentamos novos módulos conforme a necessidade específica da sua empresa. O ERP se molda ao seu negócio, não o contrário. +

+
+
+
+ + +
+
+
+
+ +

Fluxo de Caixa Pro

+
+
+
+ SOLUÇÃO ESPECIALIZADA +

Domine suas Finanças com Precisão Cirúrgica

+

+ Para empresas que buscam um controle financeiro ainda mais profundo, oferecemos o Fluxo de Caixa Pro. Uma ferramenta dedicada para DRE gerencial, conciliação bancária automática e projeções de caixa avançadas. +

+ + Conhecer Fluxo de Caixa Pro + +
+
+
+
+
+ + +
+
+
+

Pronto para transformar sua gestão?

+

O ERP Syspanel é a ferramenta que faltava para sua empresa atingir o próximo nível.

+ +
+
+
+ +@endsection diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php new file mode 100755 index 0000000..a8b90bf --- /dev/null +++ b/resources/views/errors/403.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.app') + +@section('title', 'Acesso Negado') + +@section('content') +
+
+
+
+ +
+

403

+

Acesso Negado

+

+ Você não tem permissão para acessar esta seção do sistema.
+ Entre em contato com o administrador caso precise de acesso. +

+ + Voltar ao Dashboard + +
+
+
+@endsection diff --git a/resources/views/errors/maintenance.blade.php b/resources/views/errors/maintenance.blade.php new file mode 100755 index 0000000..fd7fb34 --- /dev/null +++ b/resources/views/errors/maintenance.blade.php @@ -0,0 +1,57 @@ + + + + + + Site em Manutenção - {{ config('app.name') }} + + + + + +
+
+ +
+

Site em Manutenção

+

+ Estamos realizando algumas melhorias em nosso site para oferecer uma experiência ainda melhor. + Voltaremos em breve! +

+
+ + Entrar em Contato + + @auth + @if(Auth::user()->user_type === 'Admin') + + Ir para o Painel + + @endif + @endauth +
+

© {{ date('Y') }} {{ config('app.name') }}. Todos os direitos reservados.

+
+ + diff --git a/resources/views/faq.blade.php b/resources/views/faq.blade.php new file mode 100755 index 0000000..0ed2a86 --- /dev/null +++ b/resources/views/faq.blade.php @@ -0,0 +1,52 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page ? $page->meta_description : 'Perguntas frequentes sobre a Syspanel.') +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') +
+
+

Perguntas Frequentes

+

Tire suas dúvidas rápidas sobre a Syspanel.

+
+ +
+
+
+ @forelse($faqs as $index => $faq) +
+

+ +

+
+
+ {!! nl2br(e($faq->answer)) !!} +
+
+
+ @empty +
+ +

Nenhuma pergunta cadastrada no momento.

+
+ @endforelse +
+ +
+

Ainda tem dúvidas?

+ + Entre em Contato Conosco + +
+
+
+
+@endsection diff --git a/resources/views/fluxo-de-caixa-pro.blade.php b/resources/views/fluxo-de-caixa-pro.blade.php new file mode 100755 index 0000000..737d3e1 --- /dev/null +++ b/resources/views/fluxo-de-caixa-pro.blade.php @@ -0,0 +1,230 @@ +@extends('layouts.app') + +@section('title', $page->title ?? 'Fluxo de Caixa Pro - Gestão Financeira Inteligente') +@section('meta_description', $page->meta_description ?? 'Domine as finanças da sua empresa com o Fluxo de Caixa Pro. Controle total, DRE automático, IA financeira e acesso de qualquer lugar.') +@section('meta_keywords', $page->meta_keywords ?? 'fluxo de caixa, gestão financeira, dre gerencial, conciliação bancária, ia financeira, syspanel') + +@section('content') + + + +
+
+
+
+ + PRECISÃO FINANCEIRA ABSOLUTA + +

Fluxo de Caixa Pro: O Coração Financeiro do Seu Negócio

+

+ Muito mais que uma planilha. Uma plataforma inteligente que antecipa cenários, automatiza processos e garante que você nunca mais seja pego de surpresa pelo saldo da conta. +

+ +
+
+
+
+
+ +
+
+
IA Financeira Ativa
+ Analisando tendências em tempo real +
+
+
+
+ Saúde do Caixa + 98% +
+
+
+
+
+

"A projeção para os próximos 30 dias indica crescimento de 15% na liquidez corrente."

+
+
+
+
+
+ + +
+
+
+
+ Fluxo de Caixa +
+
+

O que é o Fluxo de Caixa e para que serve?

+

+ O Fluxo de Caixa é o registro de todas as entradas e saídas de dinheiro da sua empresa. Ele serve como o termômetro da saúde financeira do seu negócio. +

+
+
+
+ +
+
Entradas
+

Vendas, recebimentos e aportes.

+
+
+
+
+
+ +
+
Saídas
+

Pagamentos, custos e despesas.

+
+
+
+
+

+ Sem esse controle, você navega no escuro. O Fluxo de Caixa Pro traz a luz necessária para você enxergar onde cada centavo está sendo aplicado. +

+
+
+
+
+ + +
+
+
+

Por que o controle rigoroso é vital?

+

A diferença entre o sucesso e o fechamento de uma empresa está na gestão do caixa.

+
+
+
+
+
+

Previsibilidade

+

Saiba exatamente quanto terá em caixa daqui a 15, 30 ou 90 dias com base em dados reais e IA.

+
+
+
+
+
+

Tomada de Decisão

+

Posso investir agora? Tenho fôlego para uma nova contratação? Decida baseado em números, não em intuição.

+
+
+
+
+
+

Segurança Financeira

+

Evite o pagamento de juros bancários e multas por atraso através de um cronograma de pagamentos eficiente.

+
+
+
+
+
+ + +
+
+
+
+

Acesso Global & Inteligência Artificial

+

O Fluxo de Caixa Pro foi construído com as tecnologias mais modernas do mercado.

+
+
+
    +
  • 100% em Nuvem: Acesse de onde estiver, pelo celular ou computador.
  • +
  • IA Preditiva: Antecipação de inadimplência e sugestões de economia.
  • +
+
+
+
    +
  • Conciliação Bancária
  • +
  • Sistema Multiusuário: Segurança e controle total de acessos para sua equipe.
  • +
+
+
+ +
+
+
+
+ + +
+
+

Assuma o controle total hoje mesmo!

+

Junte-se aos empresários que profissionalizaram sua gestão financeira com o Fluxo de Caixa Pro.

+ + +
+
+@endsection diff --git a/resources/views/gallery-category.blade.php b/resources/views/gallery-category.blade.php new file mode 100755 index 0000000..1d62510 --- /dev/null +++ b/resources/views/gallery-category.blade.php @@ -0,0 +1,63 @@ +@extends('layouts.app') + +@section('title', $category->name . ' - Galerias') +@section('meta_description', $category->description ?? 'Galerias na categoria ' . $category->name) + +@section('content') +
+
+

{{ $category->name }}

+ @if($category->description) +

{{ $category->description }}

+ @endif +
+ + @if($galleries->count() > 0) + + +
+ {{ $galleries->links() }} +
+ @else +
+ +

Nenhuma galeria nesta categoria no momento.

+
+ @endif + + +
+@endsection + +@push('css') + +@endpush diff --git a/resources/views/gallery-show.blade.php b/resources/views/gallery-show.blade.php new file mode 100755 index 0000000..c606dd7 --- /dev/null +++ b/resources/views/gallery-show.blade.php @@ -0,0 +1,136 @@ +@extends('layouts.app') + +@section('title', $gallery->title . ' - Galeria') +@section('meta_description', $gallery->description ?? 'Galeria de fotos.') + +@section('content') +
+
+

{{ $gallery->title }}

+ @if($gallery->description) +

{{ $gallery->description }}

+ @endif +
+ + + + +
+ + + +@endsection + +@push('css') + +@endpush + +@push('js') + +@endpush diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php new file mode 100755 index 0000000..741a47d --- /dev/null +++ b/resources/views/home.blade.php @@ -0,0 +1,201 @@ +@extends('layouts.admin') + +@section('title', 'Dashboard') + +@section('content') + +
+

Dashboard

+
+ + +
+ +
+
+
+
+
+
+ Chamados Pendentes
+
{{ $metrics['pending_tickets'] }}
+
+
+ +
+
+
+
+
+ + @if(isset($metrics['total_products'])) + +
+
+
+
+
+
+ Total de Produtos
+
{{ $metrics['total_products'] }}
+
+
+ +
+
+
+
+
+ @endif + + @if(isset($metrics['total_services'])) + +
+
+
+
+
+
+ Total de Serviços
+
{{ $metrics['total_services'] }}
+
+
+ +
+
+
+
+
+ @endif + + @if(Auth::user()->isAdmin()) + +
+
+
+
+
+
+ Usuários Cadastrados
+
{{ $metrics['total_users'] }}
+
+
+ +
+
+
+
+
+ @endif +
+ + +
+ +
+
+
+
+
+
+ Mensagens Recebidas
+
{{ $metrics['total_contacts'] }}
+
+
+ +
+
+
+
+
+
+ + +
+
+ @if(session('success')) +
{{ session('success') }}
+ @endif + +
+
+
+ Bem-vindo, {{ Auth::user()->name }}! + {{ Auth::user()->user_type }} +
+
+
+

Você está logado no painel administrativo da {{ $company->name }}.

+ @if(Auth::user()->isAdmin()) +

Como Administrador, você tem acesso total ao sistema: gerenciar usuários, configurar permissões, serviços, produtos e todas as funcionalidades.

+ @else +

Aqui você poderá gerenciar as seções do sistema que o administrador liberou para seu acesso. Utilize o menu lateral para navegar.

+ @endif +
+
+ + +
+
+
+
+ + Evolução Contínua + +

Seu site pode ir muito além!

+

O SysSiteCMS foi projetado para crescer junto com o seu negócio. Se você sente que seu painel precisa de algo novo e exclusivo, nós podemos criar.

+
+ +
    +
  • + + Soluções personalizadas para o seu fluxo +
  • +
  • + + Recursos sob medida para sua empresa +
  • +
  • + + Inovação e tecnologia de ponta +
  • +
  • + + Sua ideia transformada em ferramenta +
  • +
+ +
+
Fale com nossos especialistas:
+ + contato@syspanel.com.br + +
+
+
+
+

Qual é o próximo passo para o seu site?

+

Compartilhe sua visão conosco. Descreva o que facilitaria o seu dia a dia ou qual novo desafio seu site precisa enfrentar hoje.

+ +
+ @csrf +
+ + + @error('feature_description')
{{ $message }}
@enderror +
+ +
+

+ Sua solicitação será enviada com segurança para nossa equipe técnica. +

+
+
+
+
+
+
+@endsection diff --git a/resources/views/index.blade.php b/resources/views/index.blade.php new file mode 100755 index 0000000..2fa9b3b --- /dev/null +++ b/resources/views/index.blade.php @@ -0,0 +1,284 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') + + +
+
+
+
+
+ + Inovação e Tecnologia + +
+

+ Transforme Seu Negócio com Sistemas Inteligentes +

+

+ Desenvolvemos soluções personalizadas em ERP, Frameworks PHP, Hospedagem e CMS para impulsionar o crescimento da sua empresa. +

+
+ @if(module_enabled('services')) + + Conheça Nossos Serviços + + @endif + + Fale Conosco + +
+
+
+
+
+ +

Syspanel Suite

+

Soluções completas para gestão e desenvolvimento

+
+
+
+
+
+
+
+ + + +
+
+
+ + Nossos Produtos + +

Soluções Completas para Seu Negócio

+

+ Oferecemos uma suite completa de produtos e serviços desenvolvidos com tecnologia de ponta para atender todas as necessidades da sua empresa. +

+
+ +
+
+
+
+
+
+ +
+

SysERP

+
+

+ Plataforma online completa para integrar e gerenciar todas as áreas da sua empresa: financeiro, vendas, estoque, produção e serviços. +

+
    +
  • Integração total de processos
  • +
  • Controle em tempo real
  • +
  • Personalização sob medida
  • +
+ + Acessar SysERP + +
+
+
+ +
+
+
+
+
+ +
+

Fluxo de Caixa Pro

+
+

+ Sistema especializado para controle rigoroso de fluxo de caixa, DRE gerencial, conciliação bancária e previsibilidade financeira total. +

+
    +
  • DRE Gerencial Automático
  • +
  • Conciliação Bancária OFX
  • +
  • Gestão de Inadimplência
  • +
+ + Acessar Fluxo de Caixa Pro + +
+
+
+ +
+
+
+
+
+ +
+

SysCMS

+
+

+ Sistema de gerenciamento de conteúdo potente com suporte multi-idioma, SEO avançado e controle de acesso granular. +

+
    +
  • Arquitetura modular expansível
  • +
  • Suporte nativo a 5 idiomas
  • +
  • Editor intuitivo e SEO otimizado
  • +
+ + Acessar SysCMS + +
+
+
+ +
+
+
+
+
+ +
+

SysHost

+
+

+ Hospedagem profissional com planos personalizados para sites, aplicativos, Laravel, WordPress e projetos SysMVC. +

+
    +
  • Infraestrutura de alto desempenho
  • +
  • Suporte especializado 24/7
  • +
  • Planos flexíveis e personalizados
  • +
+ + Acessar SysHost + +
+
+
+ +
+
+
+
+
+ +
+

SysVector

+
+

+ Framework PHP moderno com Bootstrap 5.3, Alpine.js e templates Twig para desenvolvimento web rápido e eficiente. +

+
    +
  • Bootstrap 5.3 completo
  • +
  • Componentes Alpine.js
  • +
  • Exemplos CRUD Twig
  • +
+ + Acessar SysVector + +
+
+
+ +
+
+
+
+
+ +
+

SysMVC

+
+

+ Framework PHP poderoso e flexível para desenvolvimento de aplicações web com arquitetura MVC profissional. +

+
    +
  • Arquitetura MVC robusta
  • +
  • Flexibilidade total
  • +
  • Ideal para projetos complexos
  • +
+ + Acessar SysMVC + +
+
+
+
+
+
+ +
+
+
+
+

Pronto para Transformar Seu Negócio?

+

+ Entre em contato conosco e descubra como nossas soluções podem impulsionar o crescimento da sua empresa. Nossa equipe está pronta para atendê-lo. +

+ +
+
+
+ +

Parceria de Sucesso

+

Juntos vamos construir o futuro do seu negócio

+
+
+
+
+
+@endsection diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php new file mode 100755 index 0000000..24be9d6 --- /dev/null +++ b/resources/views/layouts/admin.blade.php @@ -0,0 +1,617 @@ + + + + + + + SysSiteCMS - Admin - @yield('title', 'Dashboard') + + + + + + + + + + + + + + + @stack('css') + + + + + +
+ + + + + + +
+ + + + + + +
+
+ +
+
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + @stack('js') + +
+ @csrf +
+ + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100755 index 0000000..2a6a596 --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,630 @@ + + + + + + + + + @yield('title', $company->name){{ \App\Models\Setting::get('seo_title_suffix', ' - SysSiteCMS') }} + + @hasSection('meta_keywords')@endif + + + + + + + + + + + {!! \App\Models\Setting::get('scripts_head_custom') !!} + @if($gaId = \App\Models\Setting::get('scripts_google_analytics')) + + + + @endif + + + + + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + @stack('css') + + + + + + +
+ @yield('content') +
+ + +
+
+
+
+ +
+ +
+
Links Rápidos
+
    + @if(in_array('index', $activePages)) +
  • Início
  • + @endif + @if(in_array('servicos', $activePages)) +
  • Serviços
  • + @endif + @if(in_array('contato', $activePages)) +
  • Contato
  • + @endif + @if(in_array('politica-privacidade', $activePages)) +
  • Privacidade e Termos
  • + @endif +
+
+ +
+
Onde Estamos
+ +
+
+ +
+ +
+
+

© {{ date('Y') }} {{ $company->name }}. Todos os direitos reservados.

+
+
+

Desenvolvido por SysPanel com SysSiteCMS

+
+
+
+
+ + + @if($whatsapp_enabled && $company->phone) + + + + @endif + + + + + + + + + @stack('js') + + @if(module_enabled('chatbot')) + @includeIf('chatbot::partials.chat-widget') + @endif + + diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php new file mode 100755 index 0000000..98a378a --- /dev/null +++ b/resources/views/login.blade.php @@ -0,0 +1,67 @@ +@extends('layouts.app') + +@section('title', 'Login - Syspanel') + +@section('content') +
+
+
+
+
+
+ +

Bem-vindo de volta!

+

Acesse sua conta para gerenciar seus serviços.

+
+
+
+ @csrf +
+ +
+ + +
+
+ +
+
+ + Esqueceu a senha? +
+
+ + + +
+
+ +
+ + +
+ + + +
+ Não tem uma conta? + Fale conosco +
+
+
+
+
+ + +
+
+
+@endsection diff --git a/resources/views/news-index.blade.php b/resources/views/news-index.blade.php new file mode 100755 index 0000000..d1e90d0 --- /dev/null +++ b/resources/views/news-index.blade.php @@ -0,0 +1,76 @@ +@extends('layouts.app') + +@section('title', 'Notícias' . \App\Models\Setting::get('seo_title_suffix', ' - SysSiteCMS')) +@section('meta_description', 'Acompanhe as últimas notícias e novidades.') + +@section('content') +
+
+

Notícias

+

Acompanhe as últimas novidades e informações.

+
+ + @if($newsList->count() > 0) +
+ @foreach($newsList as $item) +
+
+ @if($item->featured_image) + + {{ $item->title }} + + @else + +
+ +
+
+ @endif +
+
+ {{ $item->title }} +
+ @if($item->summary) +

{{ Str::limit($item->summary, 150) }}

+ @endif +
+ @if($item->author) + {{ $item->author }} + @endif + @if($item->published_at) + {{ $item->published_at->format('d/m/Y') }} + @else + {{ $item->created_at->format('d/m/Y') }} + @endif +
+
+
+
+ @endforeach +
+ +
+ {{ $newsList->links() }} +
+ @else +
+ +

Nenhuma notícia publicada no momento.

+
+ @endif + + +
+@endsection + +@push('css') + +@endpush diff --git a/resources/views/news-show.blade.php b/resources/views/news-show.blade.php new file mode 100755 index 0000000..ba96fc2 --- /dev/null +++ b/resources/views/news-show.blade.php @@ -0,0 +1,161 @@ +@extends('layouts.app') + +@section('title', ($news->seo_title ?: $news->title) . \App\Models\Setting::get('seo_title_suffix', ' - SysSiteCMS')) +@section('meta_description', $news->seo_description ?: Str::limit($news->summary ?? $news->title, 160)) +@section('meta_keywords', $news->seo_keywords) +@section('og_title', $news->title) +@section('og_description', Str::limit($news->summary ?? $news->title, 200)) +@section('og_image', $news->featured_image ? asset('storage/' . $news->featured_image) : '') + +@section('content') +
+
+ +
+

{{ $news->title }}

+
+ @if($news->author) + {{ $news->author }} + @endif + {{ ($news->published_at ?? $news->created_at)->format('d/m/Y H:i') }} +
+
+ + + @if($news->featured_image) +
+ {{ $news->title }} +
+ @endif + + + @if($news->summary) +
+

{{ $news->summary }}

+
+ @endif + + + @if($news->content) +
+ {!! nl2br(e($news->content)) !!} +
+ @endif + + + @if($news->images->count() > 0) +
+

Galeria de Imagens

+
+ @foreach($news->images as $index => $image) +
+
+ {{ $image->description }} + @if($image->description) +
+ {{ $image->description }} +
+ @endif +
+
+ @endforeach +
+
+ @endif +
+ +
+ + +
+ + +@if($news->images->count() > 0) + +@endif +@endsection + +@push('css') + +@endpush + +@if($news->images->count() > 0) +@push('js') + +@endpush +@endif diff --git a/resources/views/page.blade.php b/resources/views/page.blade.php new file mode 100755 index 0000000..e48bda9 --- /dev/null +++ b/resources/views/page.blade.php @@ -0,0 +1,15 @@ +@extends('layouts.app') + +@section('title', $page->title) +@section('meta_description', $page->meta_description) +@section('meta_keywords', $page->meta_keywords) +@section('og_title', $page->og_title) +@section('og_description', $page->og_description) +@section('og_image', $page->og_image) +@section('canonical', $page->canonical_url) + +@section('content') +
+ {!! $page->content !!} +
+@endsection diff --git a/resources/views/partials/captcha.blade.php b/resources/views/partials/captcha.blade.php new file mode 100755 index 0000000..0fb48ca --- /dev/null +++ b/resources/views/partials/captcha.blade.php @@ -0,0 +1,21 @@ + +
+
+ +
+ @php + $n1 = rand(1, 9); + $n2 = rand(1, 9); + session(['captcha_result' => $n1 + $n2]); + @endphp +
+ {{ $n1 }} + {{ $n2 }} = ? +
+
+ + @error('captcha')
{{ $message }}
@enderror +
+
+ Por favor, resolva a conta acima para provar que você não é um robô. +
+
diff --git a/resources/views/politica-privacidade.blade.php b/resources/views/politica-privacidade.blade.php new file mode 100755 index 0000000..1279aaa --- /dev/null +++ b/resources/views/politica-privacidade.blade.php @@ -0,0 +1,86 @@ +@extends('layouts.app') + +@section('title', 'Política de Privacidade e Termos de Uso - ' . $company->name) +@section('meta_description', 'Conheça nossos termos de uso e política de privacidade em conformidade com a LGPD (Lei Geral de Proteção de Dados).') + +@section('content') +
+
+
+
+
+

Política de Privacidade e Termos de Uso

+

Última atualização: {{ date('d/m/Y') }}

+ +
+

1. Introdução e LGPD

+

+ A {{ $company->name }}, em conformidade com a Lei Geral de Proteção de Dados Pessoais (Lei nº 13.709/2018 - LGPD), + reforça seu compromisso com a transparência e a segurança no tratamento dos seus dados pessoais. + Esta política explica como coletamos, usamos e protegemos suas informações. +

+
+ +
+

2. Coleta de Dados

+

Coletamos informações das seguintes formas:

+
    +
  • Informações fornecidas por você: Dados como nome, e-mail, telefone e empresa preenchidos em nossos formulários de contato ou cadastro.
  • +
  • Dados de navegação: Através de cookies e tecnologias similares, coletamos dados sobre seu endereço IP, tipo de navegador e páginas visitadas para melhorar sua experiência.
  • +
+
+ +
+

3. Finalidade do Tratamento

+

Seus dados são utilizados para:

+
    +
  • Responder a solicitações de suporte e orçamentos;
  • +
  • Enviar comunicações de marketing e novidades (quando autorizado);
  • +
  • Cumprir obrigações legais e regulatórias;
  • +
  • Melhorar a segurança e performance do nosso site.
  • +
+
+ +
+

4. Seus Direitos (LGPD)

+

Como titular dos dados, você possui os seguintes direitos garantidos pela lei brasileira:

+
    +
  • Confirmação da existência de tratamento;
  • +
  • Acesso aos seus dados pessoais;
  • +
  • Correção de dados incompletos ou inexatos;
  • +
  • Anonimização, bloqueio ou eliminação de dados desnecessários;
  • +
  • Revogação do consentimento a qualquer momento.
  • +
+
+ +
+

5. Política de Cookies

+

+ Utilizamos cookies essenciais para o funcionamento do site e cookies analíticos para entender como você interage com nossa plataforma. + Você pode gerenciar suas preferências de cookies através das configurações do seu navegador. +

+
+ +
+

6. Termos de Uso

+

Ao utilizar este site, você concorda em:

+
    +
  • Fornecer informações verdadeiras e atualizadas;
  • +
  • Não utilizar o site para atividades ilícitas ou que violem direitos de terceiros;
  • +
  • Respeitar os direitos de propriedade intelectual da {{ $company->name }}.
  • +
+
+ +
+

7. Contato

+

+ Para exercer seus direitos ou tirar dúvidas sobre esta política, entre em contato com nosso encarregado de dados através do e-mail: + {{ $company->email }}. +

+
+
+
+
+
+
+@endsection diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php new file mode 100755 index 0000000..a32555b --- /dev/null +++ b/resources/views/profile/edit.blade.php @@ -0,0 +1,126 @@ +@extends('layouts.admin') + +@section('title', 'Meu Perfil') + +@section('content') +
+

Meu Perfil

+
+ +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +
+
+
+
+
Informações Pessoais
+
+
+
+ @csrf + @method('PUT') + +
+
+ +
+ @if($user->avatar) + + @else + + @endif +
+
+ + + @error('avatar')
{{ $message }}
@enderror +
+ PNG, JPG ou JPEG (Máx 2MB) +
+
+ +
+ + + @error('name')
{{ $message }}
@enderror +
+ +
+ + + @error('email')
{{ $message }}
@enderror +
+ +
+ + + @error('company')
{{ $message }}
@enderror +
+ +
+
+ + + @error('website')
{{ $message }}
@enderror +
+
+ + + @error('whatsapp')
{{ $message }}
@enderror +
+
+ +
+
Segurança (Alterar Senha)
+

Deixe em branco se não desejar alterar sua senha atual.

+ +
+
+ +
+ +
+ +
+ @error('password')
{{ $message }}
@enderror +
+
+
+ +
+ +
+ +
+
+
+
+ + +
+
+
+
+
+@endsection + +@push('js') + +@endpush diff --git a/resources/views/profile/preferences.blade.php b/resources/views/profile/preferences.blade.php new file mode 100755 index 0000000..d579a66 --- /dev/null +++ b/resources/views/profile/preferences.blade.php @@ -0,0 +1,131 @@ +@extends('layouts.admin') + +@section('title', 'Minhas Preferências') + +@section('content') +
+

Preferências

+
+ +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +
+
+
+
+
Configurações da Conta
+
+
+
+ @csrf + @method('PUT') + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + @if(Auth::user()->user_type === 'Admin') +
+
+ +
+ + + Quando ativado, apenas administradores poderão acessar as páginas públicas do site. +
+
+ + + Permitir que novos usuários se cadastrem livremente na página de login. +
+
+ + + Exibir ícone do WhatsApp em todas as páginas públicas do site para contato rápido. +
+ + +
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ O botão só aparecerá no site durante os horários definidos acima. +
+
+ @endif + + +
+
+
+
+
+@push('js') + +@endpush +@endsection diff --git a/resources/views/servidores-linux.blade.php b/resources/views/servidores-linux.blade.php new file mode 100755 index 0000000..3b024ce --- /dev/null +++ b/resources/views/servidores-linux.blade.php @@ -0,0 +1,252 @@ +@extends('layouts.app') + +@section('title', $page->title ?? 'Servidores Linux: Local e Nuvem') +@section('meta_description', $page->meta_description ?? 'Implantação de servidores Linux robustos, seguros e de alta performance para sua empresa.') +@section('meta_keywords', $page->meta_keywords ?? 'Linux, Servidores, Cloud, Local, Segurança, Monitoramento, Infraestrutura') +@section('og_title', $page->og_title ?? 'Servidores Linux: Local e Nuvem') +@section('og_description', $page->og_description ?? 'Infraestrutura robusta com Linux para o crescimento do seu negócio.') +@section('og_image', $page->og_image ?? '') +@section('canonical', $page->canonical_url ?? '') + +@section('content') + + + +
+
+
+
+ INFRAESTRUTURA CORPORATIVA +

Servidores Linux de Alta Performance

+

+ Soluções sob medida para implantação de servidores locais e em nuvem. Estabilidade, segurança e o poder do código aberto para impulsionar sua empresa. +

+ +
+
+ +
+
+
+
+ + +
+
+
+
+

Por que escolher Linux?

+
+

+ O Linux é a espinha dorsal da internet moderna e das maiores infraestruturas corporativas do mundo. Suas vantagens são inquestionáveis: +

+
    +
  • + +
    Estabilidade Incomparável: Sistemas que rodam por meses ou anos sem necessidade de reinicialização.
    +
  • +
  • + +
    Segurança Robusta: Arquitetura pensada para proteção de dados e permissões granulares.
    +
  • +
  • + +
    Custo-Benefício: Sem taxas abusivas de licenciamento, permitindo maior investimento em hardware.
    +
  • +
  • + +
    Performance Otimizada: Consumo eficiente de recursos, aproveitando ao máximo seu hardware.
    +
  • +
+
+
+
+
+
+
+ +
+

Imunidade

+

Arquitetura nativamente resistente a vírus e ameaças comuns em outros sistemas.

+
+
+
+
+
+ +
+

Flexibilidade

+

Totalmente customizável para atender desde pequenos escritórios até grandes datacenters.

+
+
+
+
+
+
+
+ + +
+
+
+

Local ou Nuvem?

+

Ajudamos você a escolher a melhor arquitetura para seu negócio

+
+
+
+
+
SERVIDORES LOCAIS
+

Controle Total e Velocidade

+

Ideal para empresas que precisam de acesso rápido a grandes volumes de dados ou possuem conexões de internet instáveis.

+
    +
  • Latência zero na rede local
  • +
  • Independência total de internet para uso interno
  • +
  • Posse física dos dados e do hardware
  • +
  • Sem custos mensais de hospedagem cloud
  • +
+
+
+
+
+
SERVIDORES EM NUVEM
+

Escalabilidade e Disponibilidade

+

Perfeito para negócios com equipes remotas, necessidade de acesso externo constante e alta escalabilidade.

+
    +
  • Acesso de qualquer lugar do mundo
  • +
  • Alta disponibilidade garantida (99.9%)
  • +
  • Backup automático e redundância
  • +
  • Crescimento instantâneo de recursos
  • +
+
+
+
+
+
+ + +
+
+
+

Soluções Especializadas

+

O que podemos implementar para sua empresa

+
+
+
+
+ +
Servidor de Arquivos
+

Centralize e proteja os documentos da sua empresa com controle de acesso.

+
+
+
+
+ +
Banco de Dados
+

Instalação e tuning de MySQL, PostgreSQL e MariaDB para máxima performance.

+
+
+
+
+ +
Hospedagem Web
+

Servidores Apache ou Nginx otimizados para sites e sistemas web.

+
+
+
+
+ +
Firewall e VPN
+

Segurança de rede e acesso remoto seguro para sua equipe.

+
+
+
+
+
+ + +
+
+
+
+

Monitoramento e Segurança 24/7

+

Não apenas instalamos; cuidamos do seu servidor para que você foque no seu negócio.

+
+
+
+
Métricas em Tempo Real
+

Monitoramos CPU, Memória, Disco e Rede para prevenir lentidões.

+
+
+
+
+
+
Hardening de Sistema
+

Configurações avançadas de segurança para fechar portas e vulnerabilidades.

+
+
+
+
+
+
Backups Automatizados
+

Rotinas diárias de cópias de segurança para garantir a continuidade do negócio.

+
+
+
+
+
+

Solicite um Diagnóstico

+

Nossa equipe analisará sua infraestrutura atual e proporá a melhor solução Linux.

+ Falar com Especialista +
+
+
+
+
+ +@endsection diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100755 index 0000000..5f83753 --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,225 @@ + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite(['resources/css/app.css', 'resources/js/app.js']) + @else + + @endif + + +
+ @if (Route::has('login')) + + @endif +
+
+
+
+

Let's get started

+

With so many options available to you,
we suggest you start with the following:

+ + + +

+ v{{ app()->version() }} + + View changelog + + + + +

+
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- 13 --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + @if (Route::has('login')) + + @endif + + diff --git a/routes/console.php b/routes/console.php new file mode 100755 index 0000000..3c9adf1 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php new file mode 100755 index 0000000..8b14ce3 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,201 @@ +name('index'); +Route::get('/sobre', [PageController::class, 'about'])->name('about'); +// Note: /produtos route is registered by the Products module when enabled +// Note: /servicos route is registered by the Services module when enabled +Route::get('/assistentes-digitais-com-ia', [PageController::class, 'assistentesDigitaisIA'])->name('assistentes-digitais-ia'); +Route::get('/linux-servers', [PageController::class, 'servidoresLinux'])->name('servidores-linux'); +Route::get('/erp', [PageController::class, 'erp'])->name('erp'); +Route::get('/fluxo-de-caixa-pro', [PageController::class, 'fluxoDeCaixaPro'])->name('fluxo-de-caixa-pro'); +Route::get('/downloads', [PageController::class, 'downloads'])->name('downloads'); +Route::get('/downloads/{download}', [PageController::class, 'downloadFile'])->name('downloads.file')->middleware(['auth', 'verified']); +Route::get('/galerias/categoria/{slug}', [PageController::class, 'galleryCategory'])->name('gallery.category'); +Route::get('/galeria/{slug}', [PageController::class, 'gallery'])->name('gallery.show'); +Route::get('/noticias', [PageController::class, 'newsIndex'])->name('news.index'); +Route::get('/noticias/{slug}', [PageController::class, 'newsShow'])->name('news.show'); +Route::get('/faq', [PageController::class, 'faq'])->name('faq'); +Route::get('/contato', [PageController::class, 'contato'])->name('contato'); +Route::post('/contato', [PageController::class, 'sendContact']) + ->middleware('throttle:5,1') + ->name('contato.send'); +Route::get('/politica-privacidade', [PageController::class, 'politicaPrivacidade'])->name('politica-privacidade'); + +// Rotas de Autenticação (Login, Registro, Recuperação, Verificação) +Auth::routes(['verify' => true, 'register' => true]); + +// Dashboard (Protegido) +Route::middleware(['auth', 'verified'])->group(function () { + Route::get('/home', [HomeController::class, 'index'])->name('home'); + Route::post('/home/feature-request', [HomeController::class, 'sendFeatureRequest'])->name('home.feature.request'); + + // Perfil e Preferências + Route::get('/perfil', [ProfileController::class, 'edit'])->name('profile.edit'); + Route::put('/perfil', [ProfileController::class, 'update'])->name('profile.update'); + Route::get('/preferencias', [ProfileController::class, 'preferences'])->name('profile.preferences'); + Route::put('/preferencias', [ProfileController::class, 'updatePreferences'])->name('profile.preferences.update'); +}); + +// Área Administrativa (Apenas Admin) +Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function () { + // Gerenciamento de Usuários (Somente Admin) + Route::get('/usuarios', [App\Http\Controllers\Admin\UserController::class, 'index'])->name('users.index'); + Route::get('/usuarios/criar', [App\Http\Controllers\Admin\UserController::class, 'create'])->name('users.create'); + Route::post('/usuarios', [App\Http\Controllers\Admin\UserController::class, 'store'])->name('users.store'); + Route::get('/usuarios/{user}/editar', [App\Http\Controllers\Admin\UserController::class, 'edit'])->name('users.edit'); + Route::put('/usuarios/{user}', [App\Http\Controllers\Admin\UserController::class, 'update'])->name('users.update'); + Route::delete('/usuarios/{user}', [App\Http\Controllers\Admin\UserController::class, 'destroy'])->name('users.destroy'); + + // Gerenciamento de Tipos de Usuário (Somente Admin) + Route::get('/tipos-de-usuario', [App\Http\Controllers\Admin\UserTypeController::class, 'index'])->name('user-types.index'); + Route::get('/tipos-de-usuario/criar', [App\Http\Controllers\Admin\UserTypeController::class, 'create'])->name('user-types.create'); + Route::post('/tipos-de-usuario', [App\Http\Controllers\Admin\UserTypeController::class, 'store'])->name('user-types.store'); + Route::get('/tipos-de-usuario/{userType}/editar', [App\Http\Controllers\Admin\UserTypeController::class, 'edit'])->name('user-types.edit'); + Route::put('/tipos-de-usuario/{userType}', [App\Http\Controllers\Admin\UserTypeController::class, 'update'])->name('user-types.update'); + Route::delete('/tipos-de-usuario/{userType}', [App\Http\Controllers\Admin\UserTypeController::class, 'destroy'])->name('user-types.destroy'); +}); + +// Área Administrativa (Admin e User com permissões específicas) +Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { + + // Gerenciamento de Contatos (permissão: contacts) + Route::middleware('permission:contacts')->group(function () { + Route::get('/contatos', [App\Http\Controllers\Admin\ContactController::class, 'index'])->name('contacts.index'); + Route::get('/contatos/{contact}', [App\Http\Controllers\Admin\ContactController::class, 'show'])->name('contacts.show'); + Route::post('/contatos/{contact}/responder', [App\Http\Controllers\Admin\ContactController::class, 'reply'])->name('contacts.reply'); + Route::delete('/contatos/{contact}', [App\Http\Controllers\Admin\ContactController::class, 'destroy'])->name('contacts.destroy'); + }); + + // Gerenciamento de Assuntos (permissão: subjects) + Route::middleware('permission:subjects')->group(function () { + Route::get('/assuntos', [App\Http\Controllers\Admin\SubjectController::class, 'index'])->name('subjects.index'); + Route::get('/assuntos/criar', [App\Http\Controllers\Admin\SubjectController::class, 'create'])->name('subjects.create'); + Route::post('/assuntos', [App\Http\Controllers\Admin\SubjectController::class, 'store'])->name('subjects.store'); + Route::get('/assuntos/{subject}/editar', [App\Http\Controllers\Admin\SubjectController::class, 'edit'])->name('subjects.edit'); + Route::put('/assuntos/{subject}', [App\Http\Controllers\Admin\SubjectController::class, 'update'])->name('subjects.update'); + Route::delete('/assuntos/{subject}', [App\Http\Controllers\Admin\SubjectController::class, 'destroy'])->name('subjects.destroy'); + }); + + // Gerenciamento de Páginas (permissão: pages) + Route::middleware('permission:pages')->group(function () { + Route::get('/paginas', [App\Http\Controllers\Admin\PageController::class, 'index'])->name('pages.index'); + Route::get('/paginas/criar', [App\Http\Controllers\Admin\PageController::class, 'create'])->name('pages.create'); + Route::post('/paginas', [App\Http\Controllers\Admin\PageController::class, 'store'])->name('pages.store'); + Route::get('/paginas/{page}/editar', [App\Http\Controllers\Admin\PageController::class, 'edit'])->name('pages.edit'); + Route::put('/paginas/{page}', [App\Http\Controllers\Admin\PageController::class, 'update'])->name('pages.update'); + Route::delete('/paginas/{page}', [App\Http\Controllers\Admin\PageController::class, 'destroy'])->name('pages.destroy'); + }); + + // Gerenciamento de FAQ (permissão: faqs) + Route::middleware('permission:faqs')->group(function () { + Route::get('/faq', [App\Http\Controllers\Admin\FaqController::class, 'index'])->name('faqs.index'); + Route::get('/faq/criar', [App\Http\Controllers\Admin\FaqController::class, 'create'])->name('faqs.create'); + Route::post('/faq', [App\Http\Controllers\Admin\FaqController::class, 'store'])->name('faqs.store'); + Route::get('/faq/{faq}/editar', [App\Http\Controllers\Admin\FaqController::class, 'edit'])->name('faqs.edit'); + Route::put('/faq/{faq}', [App\Http\Controllers\Admin\FaqController::class, 'update'])->name('faqs.update'); + Route::delete('/faq/{faq}', [App\Http\Controllers\Admin\FaqController::class, 'destroy'])->name('faqs.destroy'); + }); + + // Note: Products routes are registered by the Products module when enabled + // Note: Services routes are registered by the Services module when enabled + + // Gerenciamento de Notícias (permissão: news) + Route::middleware('permission:news')->group(function () { + Route::get('/noticias', [App\Http\Controllers\Admin\NewsController::class, 'index'])->name('news.index'); + Route::post('/noticias/toggle-visibilidade', [App\Http\Controllers\Admin\NewsController::class, 'toggleVisibility'])->name('news.toggle'); + Route::get('/noticias/criar', [App\Http\Controllers\Admin\NewsController::class, 'create'])->name('news.create'); + Route::post('/noticias', [App\Http\Controllers\Admin\NewsController::class, 'store'])->name('news.store'); + Route::get('/noticias/{news}/editar', [App\Http\Controllers\Admin\NewsController::class, 'edit'])->name('news.edit'); + Route::put('/noticias/{news}', [App\Http\Controllers\Admin\NewsController::class, 'update'])->name('news.update'); + Route::delete('/noticias/{news}', [App\Http\Controllers\Admin\NewsController::class, 'destroy'])->name('news.destroy'); + Route::delete('/noticias/{news}/imagem-principal', [App\Http\Controllers\Admin\NewsController::class, 'removeFeaturedImage'])->name('news.remove-featured'); + Route::post('/noticias/{news}/imagem', [App\Http\Controllers\Admin\NewsController::class, 'uploadImage'])->name('news.upload'); + Route::put('/noticias/imagem/{image}', [App\Http\Controllers\Admin\NewsController::class, 'updateImage'])->name('news.image.update'); + Route::delete('/noticias/imagem/{image}', [App\Http\Controllers\Admin\NewsController::class, 'deleteImage'])->name('news.image.delete'); + }); + + // Gerenciamento de Downloads (permissão: downloads) + Route::middleware('permission:downloads')->group(function () { + Route::get('/downloads', [App\Http\Controllers\Admin\DownloadController::class, 'index'])->name('downloads.index'); + Route::get('/downloads/criar', [App\Http\Controllers\Admin\DownloadController::class, 'create'])->name('downloads.create'); + Route::post('/downloads', [App\Http\Controllers\Admin\DownloadController::class, 'store'])->name('downloads.store'); + Route::get('/downloads/{download}/editar', [App\Http\Controllers\Admin\DownloadController::class, 'edit'])->name('downloads.edit'); + Route::put('/downloads/{download}', [App\Http\Controllers\Admin\DownloadController::class, 'update'])->name('downloads.update'); + Route::delete('/downloads/{download}', [App\Http\Controllers\Admin\DownloadController::class, 'destroy'])->name('downloads.destroy'); + }); + + // Gerenciamento de Galerias (permissão: galleries) + Route::middleware('permission:galleries')->group(function () { + Route::get('/galerias', [App\Http\Controllers\Admin\GalleryController::class, 'index'])->name('galleries.index'); + Route::get('/galerias/criar', [App\Http\Controllers\Admin\GalleryController::class, 'create'])->name('galleries.create'); + Route::post('/galerias', [App\Http\Controllers\Admin\GalleryController::class, 'store'])->name('galleries.store'); + Route::get('/galerias/{gallery}/editar', [App\Http\Controllers\Admin\GalleryController::class, 'edit'])->name('galleries.edit'); + Route::put('/galerias/{gallery}', [App\Http\Controllers\Admin\GalleryController::class, 'update'])->name('galleries.update'); + Route::delete('/galerias/{gallery}', [App\Http\Controllers\Admin\GalleryController::class, 'destroy'])->name('galleries.destroy'); + Route::post('/galerias/{gallery}/imagem', [App\Http\Controllers\Admin\GalleryController::class, 'uploadImage'])->name('galleries.upload'); + Route::put('/galerias/imagem/{image}', [App\Http\Controllers\Admin\GalleryController::class, 'updateImage'])->name('galleries.image.update'); + Route::delete('/galerias/imagem/{image}', [App\Http\Controllers\Admin\GalleryController::class, 'deleteImage'])->name('galleries.image.delete'); + + // Categorias de Galerias + Route::get('/galerias/categorias', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'index'])->name('gallery-categories.index'); + Route::get('/galerias/categorias/criar', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'create'])->name('gallery-categories.create'); + Route::post('/galerias/categorias', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'store'])->name('gallery-categories.store'); + Route::get('/galerias/categorias/{galleryCategory}/editar', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'edit'])->name('gallery-categories.edit'); + Route::put('/galerias/categorias/{galleryCategory}', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'update'])->name('gallery-categories.update'); + Route::delete('/galerias/categorias/{galleryCategory}', [App\Http\Controllers\Admin\GalleryCategoryController::class, 'destroy'])->name('gallery-categories.destroy'); + }); + + // Área de Marketing (permissão: marketing) + Route::middleware('permission:marketing')->prefix('marketing')->name('marketing.')->group(function () { + Route::get('/', [App\Http\Controllers\Admin\MarketingController::class, 'index'])->name('index'); + Route::get('/visitantes', [App\Http\Controllers\Admin\MarketingController::class, 'analytics'])->name('analytics'); + Route::get('/promover-produtos', [App\Http\Controllers\Admin\MarketingController::class, 'promoteProducts'])->name('promote.products'); + Route::get('/promover-servicos', [App\Http\Controllers\Admin\MarketingController::class, 'promoteServices'])->name('promote.services'); + Route::get('/email-marketing', [App\Http\Controllers\Admin\MarketingController::class, 'emailMarketing'])->name('email'); + Route::post('/email-marketing', [App\Http\Controllers\Admin\MarketingController::class, 'sendEmailMarketing'])->name('email.send'); + + // Gerenciamento de Banners (Destaques) + Route::resource('banners', App\Http\Controllers\Admin\BannerController::class); + }); + + // Configurações e SEO Global (permissão: settings) + Route::middleware('permission:settings')->group(function () { + Route::get('/configuracoes', [App\Http\Controllers\Admin\SettingController::class, 'index'])->name('settings.index'); + Route::post('/configuracoes', [App\Http\Controllers\Admin\SettingController::class, 'update'])->name('settings.update'); + }); + + // ERP Syspanel Advertisement (available to all logged-in users) + Route::get('/erp-syspanel', [App\Http\Controllers\Admin\SettingController::class, 'erpAd'])->name('erp.ad'); + + // Manual de Uso (available to all logged-in users) + Route::get('/manual-de-uso', function () { + return view('admin.manual.index'); + })->name('manual'); + + // Notificações (available to all logged-in users) + Route::get('/notificacoes/{id}/lida', function ($id) { + /** @var \App\Models\User $user */ + $user = Auth::user(); + $notification = $user->notifications()->findOrFail($id); + $notification->markAsRead(); + return Redirect::to($notification->data['link']); + })->name('notifications.read'); + + Route::get('/notificacoes/limpar', function () { + /** @var \App\Models\User $user */ + $user = Auth::user(); + $user->unreadNotifications->markAsRead(); + return Redirect::back(); + })->name('notifications.clear'); +}); + +// Rota Dinâmica para Páginas Customizadas (DEVE SER SEMPRE A ÚLTIMA ROTA) +Route::get('/{slug}', [PageController::class, 'show'])->name('pages.show'); diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100755 index 0000000..fedb287 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100755 index 0000000..05c4471 --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100755 index 0000000..01e4a6c --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100755 index 0000000..8364a84 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100755 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100755 index 0000000..dbbf333 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; + +export default defineConfig({ + plugins: [ + laravel({ + input: [ + 'resources/sass/app.scss', + 'resources/js/app.js', + ], + refresh: true, + }), + ], +});