├── .editorconfig
├── .gitattributes
├── .github
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ └── feature_request.md
├── SECURITY.md
├── dependabot.yml
└── workflows
│ ├── dependabot-auto-merge.yml
│ ├── fix-php-code-style-issues.yml
│ ├── phpstan.yml
│ ├── run-tests.yml
│ └── update-changelog.yml
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README.md
├── composer.json
├── composer.lock
├── database
├── factories
│ ├── DepartmentFactory.php
│ └── LetterFactory.php
├── migrations
│ ├── alter_letters_constraints.php.stub
│ ├── create_department_table.php.stub
│ └── create_letters_table.php.stub
└── seeders
│ └── WindSeeder.php
├── docs
├── _index.md
├── changelog.md
├── configuration.md
├── customization.md
├── events.md
├── filament.md
├── installation.md
├── introduction.md
├── thems.md
├── update.md
└── usage.md
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── pint.json
├── resources
├── lang
│ ├── ar.json
│ ├── ckb.json
│ └── en.json
└── views
│ └── themes
│ └── zeus
│ └── wind
│ ├── contact-form.blade.php
│ ├── departments.blade.php
│ └── submitted.blade.php
├── routes
└── web.php
├── src
├── Commands
│ └── PublishCommand.php
├── Configuration.php
├── Events
│ ├── LetterSent.php
│ ├── NewLetterSent.php
│ └── ReplySent.php
├── Filament
│ └── Resources
│ │ ├── DepartmentResource.php
│ │ ├── DepartmentResource
│ │ └── Pages
│ │ │ ├── CreateDepartment.php
│ │ │ ├── EditDepartment.php
│ │ │ └── ListDepartments.php
│ │ ├── LetterResource.php
│ │ └── LetterResource
│ │ └── Pages
│ │ ├── CreateLetter.php
│ │ ├── EditLetter.php
│ │ └── ListLetters.php
├── Livewire
│ └── ContactsForm.php
├── Models
│ ├── Department.php
│ └── Letter.php
├── WindPlugin.php
└── WindServiceProvider.php
└── tests
├── AdminPanelProvider.php
├── ExampleTest.php
├── Pest.php
└── TestCase.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or pull request.
6 |
7 | ## Etiquette
8 |
9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code
10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be
11 | extremely unfair for them to suffer abuse or anger for their hard work.
12 |
13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
14 | world that developers are civilized and selfless people.
15 |
16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
18 |
19 | ## Viability
20 |
21 | When requesting or submitting new features, first consider whether it might be useful to others. Open
22 | source projects are used by many developers, who may have entirely different needs to your own. Think about
23 | whether or not your feature is likely to be used by other users of the project.
24 |
25 | ## Procedure
26 |
27 | Before filing an issue:
28 |
29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
30 | - Check to make sure your feature suggestion isn't already present within the project.
31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
32 | - Check the pull requests tab to ensure that the feature isn't already in progress.
33 |
34 | Before submitting a pull request:
35 |
36 | - Check the codebase to ensure that your feature doesn't already exist.
37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
38 |
39 | ## Requirements
40 |
41 | If the project maintainer has any additional requirements, you will find them listed here.
42 |
43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
44 |
45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
46 |
47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
48 |
49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
50 |
51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
52 |
53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
54 |
55 | **Happy coding**!
56 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: atmonshi
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Report an Issue or Bug with the Package
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | We're sorry to hear you have a problem. Can you help us solve it by providing the following details.
10 | - type: textarea
11 | id: what-happened
12 | attributes:
13 | label: What happened?
14 | description: What did you expect to happen?
15 | placeholder: I cannot currently do X thing because when I do, it breaks X thing.
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: how-to-reproduce
20 | attributes:
21 | label: How to reproduce the bug
22 | description: How did this occur, please add any config values used and provide a set of reliable steps if possible.
23 | placeholder: When I do X I see Y.
24 | validations:
25 | required: true
26 | - type: input
27 | id: package-version
28 | attributes:
29 | label: Package Version
30 | description: What version of our Package are you running? Please be as specific as possible
31 | placeholder: 2.0.0
32 | validations:
33 | required: true
34 | - type: input
35 | id: php-version
36 | attributes:
37 | label: PHP Version
38 | description: What version of PHP are you running? Please be as specific as possible
39 | placeholder: 8.2.0
40 | validations:
41 | required: true
42 | - type: input
43 | id: laravel-version
44 | attributes:
45 | label: Laravel Version
46 | description: What version of Laravel are you running? Please be as specific as possible
47 | placeholder: 9.0.0
48 | validations:
49 | required: true
50 | - type: dropdown
51 | id: operating-systems
52 | attributes:
53 | label: Which operating systems does with happen with?
54 | description: You may select more than one.
55 | multiple: true
56 | options:
57 | - macOS
58 | - Windows
59 | - Linux
60 | - type: textarea
61 | id: notes
62 | attributes:
63 | label: Notes
64 | description: Use this field to provide any other notes that you feel might be relevant to the issue.
65 | validations:
66 | required: false
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | If you discover any security related issues, please email info@larazeus.com instead of using the issue tracker.
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Please see the documentation for all configuration options:
2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 |
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 | labels:
12 | - "dependencies"
--------------------------------------------------------------------------------
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: dependabot-auto-merge
2 | on: pull_request_target
3 |
4 | permissions:
5 | pull-requests: write
6 | contents: write
7 |
8 | jobs:
9 | dependabot:
10 | runs-on: ubuntu-latest
11 | if: ${{ github.actor == 'dependabot[bot]' }}
12 | steps:
13 |
14 | - name: Dependabot metadata
15 | id: metadata
16 | uses: dependabot/fetch-metadata@v2.4.0
17 | with:
18 | github-token: "${{ secrets.GITHUB_TOKEN }}"
19 |
20 | - name: Auto-merge Dependabot PRs for semver-minor updates
21 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
22 | run: gh pr merge --auto --merge "$PR_URL"
23 | env:
24 | PR_URL: ${{github.event.pull_request.html_url}}
25 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
26 |
27 | - name: Auto-merge Dependabot PRs for semver-patch updates
28 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
29 | run: gh pr merge --auto --merge "$PR_URL"
30 | env:
31 | PR_URL: ${{github.event.pull_request.html_url}}
32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
--------------------------------------------------------------------------------
/.github/workflows/fix-php-code-style-issues.yml:
--------------------------------------------------------------------------------
1 | name: Fix PHP code style issues
2 |
3 | on: [push]
4 |
5 | jobs:
6 | php-code-styling:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - name: Checkout code
11 | uses: actions/checkout@v4
12 | with:
13 | ref: ${{ github.head_ref }}
14 |
15 | - name: Fix PHP code style issues
16 | uses: aglipanci/laravel-pint-action@2.5
17 |
18 | - name: Commit changes
19 | uses: stefanzweifel/git-auto-commit-action@v5
20 | with:
21 | commit_message: Fix styling
--------------------------------------------------------------------------------
/.github/workflows/phpstan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.php'
7 | - 'phpstan.neon.dist'
8 |
9 | jobs:
10 | phpstan:
11 | name: phpstan
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: '8.2'
20 | coverage: none
21 |
22 | - name: Install composer dependencies
23 | uses: ramsey/composer-install@v3
24 |
25 | - name: Run PHPStan
26 | run: ./vendor/bin/phpstan --error-format=github
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | branches: [3.x]
6 | pull_request:
7 | branches: [3.x]
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | os: [ubuntu-latest, windows-latest]
16 | php: [8.2]
17 | laravel: [10.*]
18 | stability: [prefer-stable]
19 | include:
20 | - laravel: 10.*
21 | testbench: 8.*
22 |
23 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
24 |
25 | steps:
26 | - name: Checkout code
27 | uses: actions/checkout@v4
28 |
29 | - name: Setup PHP
30 | uses: shivammathur/setup-php@v2
31 | with:
32 | php-version: ${{ matrix.php }}
33 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
34 | coverage: none
35 |
36 | - name: Setup problem matchers
37 | run: |
38 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
39 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
40 |
41 | - name: Install dependencies
42 | run: |
43 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
44 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
45 |
46 | - name: Execute tests
47 | run: vendor/bin/pest
--------------------------------------------------------------------------------
/.github/workflows/update-changelog.yml:
--------------------------------------------------------------------------------
1 | name: "Update Changelog"
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | jobs:
8 | update:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 | with:
15 | ref: 3.x
16 |
17 | - name: Update Changelog
18 | uses: stefanzweifel/changelog-updater-action@v1
19 | with:
20 | latest-version: ${{ github.event.release.name }}
21 | release-notes: ${{ github.event.release.body }}
22 |
23 | - name: Commit updated CHANGELOG
24 | uses: stefanzweifel/git-auto-commit-action@v5
25 | with:
26 | branch: 3.x
27 | commit_message: Update CHANGELOG
28 | file_pattern: CHANGELOG.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | /build
7 | /vendor/.idea
8 | /bootstrap/cache
9 | .env
10 | .env.backup
11 | .phpunit.result.cache
12 | docker-compose.override.yml
13 | Homestead.json
14 | Homestead.yaml
15 | npm-debug.log
16 | yarn-error.log
17 | /.idea
18 | /.vscode
19 | /pkg
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "all"
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Lara Zeus (Ash)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | [](https://packagist.org/packages/lara-zeus/core)
8 | [](https://packagist.org/packages/lara-zeus/wind)
9 | [](https://github.com/lara-zeus/wind/actions?query=workflow%3Arun-tests+branch%3Amain)
10 | [](https://github.com/lara-zeus/wind/actions?query=workflow%3Afix-php-code-style-issues+branch%3Amain)
11 | [](https://packagist.org/packages/lara-zeus/wind)
12 | [](https://github.com/lara-zeus/wind)
13 |
14 |
15 |
16 | Lara Zeus Wind
17 |
18 | contact form, with simple dashboard to read and replay to any messages you receive from your website.
19 | >small tasks can be time-consuming, let us build these for you,
20 |
21 | ## Support Filament
22 |
23 |
24 |
25 |
26 |
27 | ## Features
28 | - 🔥 built with [TALL stack](https://tallstack.dev/)
29 | - 🔥 using [filament](https://filamentadmin.com) as an admin panel
30 | - 🔥 optionally you can add categories to the contact form like 'sales','dev','report bug' etc.
31 | - 🔥 you can add logos for all categories.
32 | - 🔥 direct URL to contact on specific category.
33 |
34 | ## Demo
35 |
36 | > visit our website to get the full documentation: https://larazeus.com/wind
37 |
38 | > real use: https://larazeus.com/contact-us
39 |
40 | ## Installation
41 |
42 | You can install the package via composer:
43 |
44 | ```bash
45 | composer require lara-zeus/wind
46 | ```
47 |
48 | run the command:
49 |
50 | ```bash
51 | php artisan wind:publish
52 | ```
53 |
54 | ## Usage
55 |
56 | visit the url `/admin` to manage the Letters, and `/contact-us` to access the contact form.
57 |
58 | ## Full Documentation
59 |
60 | > visit our website to get the full documentation: https://larazeus.com/wind
61 |
62 | ## Quick start
63 |
64 | [](https://github.com/lara-zeus/zeus)
65 | [](https://github.com/lara-zeus/artemis)
66 |
67 | ## Changelog
68 |
69 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
70 |
71 | ## Support
72 |
73 | available support channels:
74 | * open an issue on [GitHub](https://github.com/lara-zeus/wind/issues)
75 | * email us using the [contact center](https://larazeus.com/contact-us)
76 |
77 | ## Contributing
78 |
79 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
80 |
81 | ## Security
82 |
83 | If you discover any security related issues, please email info@larazeus.com instead of using the issue tracker.
84 |
85 | ## Credits
86 |
87 | - [Lara Zeus (Ash)](https://github.com/atmonshi)
88 | - [All Contributors](../../contributors)
89 |
90 | ## License
91 |
92 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
93 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lara-zeus/wind",
3 | "description": "Zeus Wind is simple contact form for your website with easy to use dashboard, works as a plugin for Filament Admin Panel",
4 | "keywords": [
5 | "laravel",
6 | "lara-zeus",
7 | "contact",
8 | "form",
9 | "forms",
10 | "mailing",
11 | "filamentphp"
12 | ],
13 | "homepage": "https://larazeus.com/wind",
14 | "support": {
15 | "issues": "https://github.com/lara-zeus/wind/issues",
16 | "source": "https://github.com/lara-zeus/wind"
17 | },
18 | "license": "MIT",
19 | "type": "library",
20 | "authors": [
21 | {
22 | "name": "Lara Zeus (Ash)",
23 | "email": "info@larazeus.com"
24 | }
25 | ],
26 | "require": {
27 | "php": "^8.1",
28 | "lara-zeus/core": "^3.2"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^10.1",
32 | "nunomaduro/collision": "^7.0",
33 | "pestphp/pest-plugin-livewire": "2.x-dev",
34 | "phpstan/phpstan-deprecation-rules": "^1.0",
35 | "phpstan/phpstan-phpunit": "^1.0",
36 | "laravel/pint": "^1.0",
37 | "larastan/larastan": "^2.2",
38 | "orchestra/testbench": "^8.0",
39 | "pestphp/pest": "^2.0",
40 | "pestphp/pest-plugin-laravel": "^2.0",
41 | "phpstan/extension-installer": "^1.1",
42 | "phpstan/phpstan": "^1.10"
43 | },
44 | "autoload": {
45 | "psr-4": {
46 | "LaraZeus\\Wind\\": "src"
47 | }
48 | },
49 | "autoload-dev": {
50 | "psr-4": {
51 | "LaraZeus\\Wind\\Tests\\": "tests"
52 | }
53 | },
54 | "scripts": {
55 | "pint": "vendor/bin/pint",
56 | "test:pest": "vendor/bin/pest --parallel",
57 | "test:phpstan": "vendor/bin/phpstan analyse",
58 | "test": [
59 | "@test:pest",
60 | "@test:phpstan"
61 | ]
62 | },
63 | "config": {
64 | "sort-packages": true,
65 | "allow-plugins": {
66 | "pestphp/pest-plugin": true,
67 | "phpstan/extension-installer": true,
68 | "dealerdirect/phpcodesniffer-composer-installer": true
69 | }
70 | },
71 | "minimum-stability": "dev",
72 | "extra": {
73 | "laravel": {
74 | "providers": [
75 | "LaraZeus\\Wind\\WindServiceProvider"
76 | ]
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/database/factories/DepartmentFactory.php:
--------------------------------------------------------------------------------
1 | getModel('Department');
13 | }
14 |
15 | public function definition(): array
16 | {
17 | return [
18 | 'name' => $this->faker->words(3, true),
19 | 'ordering' => $this->faker->numberBetween(1, 10),
20 | 'is_active' => $this->faker->numberBetween(0, 1),
21 | 'desc' => $this->faker->words(5, true),
22 | 'slug' => $this->faker->slug,
23 | ];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/database/factories/LetterFactory.php:
--------------------------------------------------------------------------------
1 | getModel('Department');
14 | }
15 |
16 | public function definition(): array
17 | {
18 | return [
19 | 'name' => $this->faker->name,
20 | 'email' => $this->faker->email,
21 | 'department_id' => Department::factory(),
22 | 'title' => $this->faker->words(3, true),
23 | 'message' => $this->faker->words(5, true),
24 | 'status' => 'NEW',
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/database/migrations/alter_letters_constraints.php.stub:
--------------------------------------------------------------------------------
1 | dropForeign(['department_id']);
19 | Schema::enableForeignKeyConstraints();
20 |
21 | $table->foreign('department_id')
22 | ->onUpdate('cascade')
23 | ->onDelete('cascade')
24 | ->references('id')
25 | ->on('departments');
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 |
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/database/migrations/create_department_table.php.stub:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name');
19 | $table->integer('ordering')->default(1);
20 | $table->boolean('is_active')->default(0);
21 | $table->text('desc')->nullable();
22 | $table->string('slug');
23 | $table->string('logo')->nullable();
24 | $table->timestamps();
25 | $table->softDeletes();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::dropIfExists('departments');
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/database/migrations/create_letters_table.php.stub:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('name');
19 | $table->string('email');
20 | $table->foreignId('department_id')->nullable()->constrained();
21 | $table->string('title');
22 | $table->text('message');
23 | $table->string('status')->default('NEW');
24 | $table->string('reply_title')->nullable();
25 | $table->text('reply_message')->nullable();
26 | $table->timestamps();
27 | $table->softDeletes();
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | *
34 | * @return void
35 | */
36 | public function down()
37 | {
38 | Schema::dropIfExists('letters');
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/database/seeders/WindSeeder.php:
--------------------------------------------------------------------------------
1 | getModel('Department')::factory()
14 | ->has(
15 | WindPlugin::get()->getModel('Letter')::factory()
16 | ->count(5)
17 | ->state(function (array $attributes, Department $department) {
18 | return [
19 | 'department_id' => $department->id,
20 | ];
21 | })
22 | )->count(3)->create();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docs/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: v3
3 | slogan: contact form, with ready to use a frontend scaffolding to get you up and running easily.
4 | githubUrl: https://github.com/lara-zeus/wind
5 | branch: main
6 | icon: ri-windy-line
7 | ---
8 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Changelog
3 | weight: 100
4 | ---
5 |
6 | All changes to `Wind` are documented on GitHub [changelog](https://github.com/lara-zeus/wind/blob/main/CHANGELOG.md)
7 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Configuration
3 | weight: 3
4 | ---
5 |
6 | ## Configuration
7 |
8 | to configure the plugin Wind, you can pass the configuration to the plugin in `adminPanelProvider`
9 |
10 | these all the available configuration, and their defaults values
11 |
12 | ```php
13 | WindPlugin::make()
14 | ->windPrefix('contact-us')
15 | ->windMiddleware(['web'])
16 | ->defaultDepartmentId(1)
17 | ->defaultStatus('NEW')
18 | ->departmentResource()
19 | ->windModels([
20 | 'Department' => \LaraZeus\Wind\Models\Department::class,
21 | 'Letter' => \LaraZeus\Wind\Models\Letter::class,
22 | ])
23 | ->uploadDisk('public')
24 | ->uploadDirectory('logos')
25 | ->navigationGroupLabel('Wind'),
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/customization.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Customization
3 | weight: 6
4 | ---
5 |
6 | to customize the layout, you can change the default layout in the `zeus.php` config file
7 |
8 | ```php
9 | 'layout' => 'components.app',
10 | ```
11 |
12 | ## Publishing the default layout
13 |
14 | or if you don't have a layout yet, you can publish the default one:
15 |
16 | ```bash
17 | php artisan vendor:publish --tag=zeus-views
18 | ```
19 |
20 | ## Publishing the default views
21 |
22 | to customize the default views for wind:
23 |
24 | ```bash
25 | php artisan vendor:publish --tag=zeus-wind-views
26 | ```
27 |
28 | ## Publishing Translations
29 |
30 | to customize the translations:
31 |
32 | ```bash
33 | php artisan vendor:publish --tag=zeus-wind-translations
34 | ```
35 |
36 | ## themes
37 | soon
38 |
--------------------------------------------------------------------------------
/docs/events.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Events
3 | weight: 5
4 | ---
5 |
6 | ## Available Events
7 |
8 | wind will fire these events:
9 | - `LaraZeus\Wind\Events\LetterSent`
10 | when a new letter sent from the website
11 | - `LaraZeus\Wind\Events\ReplySent`
12 | when you send a replay from the admin panel
13 | - `LaraZeus\Wind\Events\NewLetterSent`
14 | when you send a new letter from the admin panel
15 |
16 | ## Register a Listener:
17 |
18 | * first create your listener:
19 | *
20 | ```bash
21 | php artisan make:listener SendWindNotification --event=LetterSent
22 | ```
23 |
24 | * second register the listener in your `EventServiceProvider`
25 |
26 | ```php
27 | protected $listen = [
28 | //...
29 | LetterSent::class => [
30 | SendWindNotification::class,
31 | ],
32 | ];
33 | ```
34 |
35 | * finally, you can receive the letter object in the `handle` method, and do what ever you want.
36 | for example:
37 |
38 | ```php
39 | Mail::to(User::first())->send(new \App\Mail\Contact(
40 | $event->letter->name, $event->letter->email, $event->letter->message
41 | ));
42 | ```
43 |
--------------------------------------------------------------------------------
/docs/filament.md:
--------------------------------------------------------------------------------
1 | # Zeus Wind
2 |
3 | Wind contact form for your website with easy to use dashboard, works as a plugin for Filament Admin Panel make it easier to customize it and match your branding.
4 |
5 | ## Features
6 |
7 | - 🔥 optionally you can add categories to the contact form like 'sales','dev','report bug' etc.
8 | - 🔥 you can add logos for all categories.
9 | - 🔥 direct URL to contact on specific category.
10 | - 🔥 Frontend scaffolding, highly customizable.
11 |
12 | ## Screenshots
13 |
14 | * **Contact form:** simple contact form allow your users to contact you
15 |
16 | 
17 | 
18 | 
19 |
20 | * **Departments:** optionally you can create departments to make it easy to manage the letters
21 |
22 | 
23 | 
24 |
25 | ## More Details
26 | **✨ to learn more about Wind the form builder, please visit:**
27 |
28 | - [Docs](https://larazeus.com/docs/wind)
29 | - [Github](https://github.com/lara-zeus/wind)
30 | - [Demo](https://demo.larazeus.com)
31 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | weight: 2
4 | ---
5 |
6 | ## Composer
7 |
8 | You can install the package via composer:
9 |
10 | ```bash
11 | composer require lara-zeus/wind
12 | ```
13 |
14 | ## publish
15 | for your convenient, we create a one command to publish them all:
16 |
17 | ```bash
18 | php artisan wind:publish
19 | ```
20 |
21 | you can pass `--force` option to force publishing all the files, helps if you're updating the package
22 |
23 | ## Migrations
24 | to just publish the migrations files
25 |
26 | ```bash
27 | php artisan vendor:publish --tag=zeus-wind-migrations
28 | ```
29 |
30 | ## Seeder and Factories
31 |
32 | optionally, if you want to seed the database, publish the seeder and factories with:
33 |
34 | ```bash
35 | php artisan vendor:publish --tag=zeus-wind-seeder
36 | php artisan vendor:publish --tag=zeus-wind-factories
37 | ```
38 |
39 | ## Assets
40 |
41 | to publish the assets files for the frontend:
42 |
43 | ```bash
44 | php artisan vendor:publish --tag=zeus-assets
45 | ```
46 |
47 | ## Run Migration
48 |
49 | finally, run the migration:
50 |
51 | ```bash
52 | php artisan migrate
53 | ```
54 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | weight: 1
4 | ---
5 |
6 | ## Introduction
7 |
8 | Lara-zeus Wind, is a package provides a simple contact form manger, with the abilety to store the messages in the database, and you can reply to them from the dashboard.
9 |
10 | **[Demo](https://wind.larazeus.com) · [Source Code](https://github.com/lara-zeus/wind) · [Discord](https://discord.com/channels/883083792112300104/1282763077611421829)**
11 |
12 | ## features:
13 |
14 | 🔥 built with [TALL stack](https://tallstack.dev/)
15 | 🔥 using [filament](https://filamentadmin.com) as an admin panel
16 | 🔥 optionally you can add departments to the contact form like 'sales','dev','report bug' etc.
17 | 🔥 you can add logos for all departments.
18 | 🔥 direct URL to contact on specific department.
19 |
20 | ## Support
21 |
22 | available support channels:
23 | * Join our channel on [Discord](https://discord.com/channels/883083792112300104/1282763077611421829)
24 | * open an issue on [GitHub](https://github.com/lara-zeus/wind/issues)
25 | * email us using the [contact center](https://larazeus.com/contact-us)
26 |
--------------------------------------------------------------------------------
/docs/thems.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Theming
3 | weight: 20
4 | ---
5 |
6 | ## Tailwind config
7 |
8 | ### for filament
9 | We only have small classes used in `LetterResource`, add to your tailwind config `content`
10 |
11 | ```
12 | './vendor/lara-zeus/wind/src/Filament/Resources/LetterResource.php',
13 | ```
14 |
15 | ### For the frontend
16 | If you have a custom theme you should add the blade files:
17 |
18 | ```
19 | './vendor/lara-zeus/wind/resources/views/themes/**/*.blade.php',
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/update.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Updating Wind
3 | weight: 99
4 | ---
5 |
6 | ## Composer
7 |
8 | to update the package first run:
9 |
10 | ```bash
11 | composer update
12 | ```
13 |
14 | ## Publishing the files
15 |
16 | then run the same command to publish any new files
17 |
18 | ```bash
19 | php artisan wind:publish
20 | ```
21 |
22 | > updating to wind V2 may introduce some braking changes if you already published the views and the configuration file.
23 | we recommend running the `publish` command with the flag: `--force` and check your changes.
24 |
25 | ```bash
26 | php artisan wind:publish --force
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Usage
3 | weight: 4
4 | ---
5 |
6 | ## Usage
7 | after you finished installing `wind` all you have to do is visit the url `/admin` to manage the Letters,
8 | and `/wind` to access the contact form.
9 |
10 | check the configuration section for more details
11 |
12 | if you don't have a user, or it's a fresh installation of laravel, you can use the command to create a new user
13 | ```bash
14 | php artisan make:filament-user
15 | ```
16 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lara-zeus/wind/0269ae99eca2f0f7297504787272ee61057e9d67/phpstan-baseline.neon
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | level: 6
6 | paths:
7 | - src
8 | - database
9 |
10 | checkOctaneCompatibility: true
11 | checkModelProperties: true
12 |
13 | ignoreErrors:
14 | -
15 | identifier: missingType.iterableValue
16 | -
17 | identifier: missingType.generics
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 | tests
24 |
25 |
26 |
27 |
28 | ./src
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/pint.json:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "laravel",
3 | "rules": {
4 | "blank_line_before_statement": true,
5 | "concat_space": {
6 | "spacing": "one"
7 | },
8 | "method_argument_space": true,
9 | "single_trait_insert_per_statement": true,
10 | "types_spaces": {
11 | "space": "single"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/resources/lang/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "Department": "قسم",
3 | "Departments": "الأقسام",
4 | "name": "الإسم",
5 | "desc": "الوصف",
6 | "ordering": "الترتيب",
7 | "is active": "مفعل؟",
8 | "Inactive": "غير مفعل؟",
9 | "slug": "الاختصار",
10 | "logo": "الشعار",
11 | "Letter": "رسالة",
12 | "Letters": "الرسائل",
13 | "from": "المرسل",
14 | "title": "عنوان الرسالة",
15 | "department": "القسم",
16 | "status": "الحالة",
17 | "status_NEW": "جديد",
18 | "status_READ": "مقروء",
19 | "status_REPLIED": "تم الرد",
20 | "status_SENT": "تم الإرسال",
21 | "reply_message": "نص الرد",
22 | "reply_title": "عنوان الرد",
23 | "message": "الرسالة",
24 | "email": "البريد",
25 | "re": "رد",
26 | "Contact us": "اتصل بنا",
27 | "feel free to contact us.": "نرحب بك في التواصل معنا.",
28 | "Send": "ارسال",
29 | "no departments available!": "لاتوجد اقسام متاحة!",
30 | "Select Department": "اختر القسم",
31 | "we received your message, and will get back to you shortly.": "تم استلام رسالتك وسيتم الرد عليها في اقرب وقت ممكن",
32 | "Edit": "تعديل",
33 | "Open": "فتح",
34 | "Delete": "حذف",
35 | "sent at": "ارسلت بتاريخ"
36 | }
37 |
--------------------------------------------------------------------------------
/resources/lang/ckb.json:
--------------------------------------------------------------------------------
1 | {
2 | "Department": "بەش",
3 | "Departments": "بەشەکان",
4 | "name": "ناو",
5 | "desc": "دابەزین",
6 | "ordering": "ڕیزکردن",
7 | "is active": "چالاکە",
8 | "slug": "سڵەگ",
9 | "logo": "لۆگۆ",
10 | "Letter": "نامە",
11 | "Letters": "نامەکان",
12 | "from": "لە",
13 | "title": "ناونیشان",
14 | "department": "بەش",
15 | "status": "دۆخ",
16 | "status_NEW": "نوێ",
17 | "status_READ": "خوێندنەوە",
18 | "status_REPLIED": "وەڵامدراوە",
19 | "reply_message": "پەیامی وەڵامدانەوە",
20 | "reply_title": "ناونیشانی وەڵامدانەوە",
21 | "message": "پەیام",
22 | "email": "ئیمەیڵ",
23 | "re": "دەربارەی",
24 | "Contact us": "پەیوەندیمان پێوەبکە",
25 | "feel free to contact us.": "ئازادانە پەیوەندیمان پێوە بکەن.",
26 | "Send": "ناردن",
27 | "no departments available!": "هیچ بەشێک لەبەردەستدا نییە!",
28 | "Select Department": "بەشێک هەڵبژێرە",
29 | "we received your message, and will get back to you shortly.": "پەیامەکەتمان پێگەیشت، و بەم زووانە پەیوەندیت پێوە دەکەینەوە.",
30 | "Edit": "دەستکاریکردن",
31 | "Open": "کردنەوە",
32 | "Delete": "سڕینەوە",
33 | "sent at": "نێردراوە لە",
34 | "Inactive": "ناچالاک"
35 | }
36 |
--------------------------------------------------------------------------------
/resources/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Department": "Department",
3 | "Departments": "Departments",
4 | "name": "name",
5 | "desc": "desc",
6 | "ordering": "ordering",
7 | "is active": "is active",
8 | "slug": "slug",
9 | "logo": "logo",
10 | "Letter": "Letter",
11 | "Letters": "Letters",
12 | "from": "from",
13 | "title": "title",
14 | "department": "department",
15 | "status": "status",
16 | "status_NEW": "NEW",
17 | "status_READ": "READ",
18 | "status_REPLIED": "REPLIED",
19 | "status_SENT": "SENT",
20 | "reply_message": "reply message",
21 | "reply_title": "reply title",
22 | "message": "message",
23 | "email": "email",
24 | "re": "re",
25 | "Contact us": "Contact us",
26 | "feel free to contact us.": "feel free to contact us.",
27 | "Send": "Send",
28 | "no departments available!": "no departments available!",
29 | "Select Department": "Select Department",
30 | "we received your message, and will get back to you shortly.": "we received your message, and will get back to you shortly.",
31 | "Edit": "Edit",
32 | "Open": "Open",
33 | "Delete": "Delete",
34 | "sent at": "sent at",
35 | "Inactive": "Inactive"
36 | }
37 |
--------------------------------------------------------------------------------
/resources/views/themes/zeus/wind/contact-form.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ __('Contact us') }}
4 |
5 |
6 |
7 |
8 | {{ __('Contact us') }}
9 |
10 |
11 |
12 |
13 |
14 | {{ __('feel free to contact us.') }}
15 |
16 |
17 |
18 | @php
19 | $colors = \Illuminate\Support\Arr::toCssStyles([
20 | \Filament\Support\get_color_css_variables('primary', shades: [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]),
21 | ]);
22 | @endphp
23 |
24 | @if($sent)
25 | @include(app('windTheme').'.submitted')
26 | @else
27 |
37 | @endif
38 |
39 |
--------------------------------------------------------------------------------
/resources/views/themes/zeus/wind/departments.blade.php:
--------------------------------------------------------------------------------
1 |
5 |
6 | @if(\LaraZeus\Wind\WindPlugin::get()->hasDepartmentResource())
7 | @php $departments = \LaraZeus\Wind\WindPlugin::get()->getModel('Department')::departments()->get(); @endphp
8 | @if($departments->isEmpty())
9 |
10 |
11 | {{ __('no departments available!') }}
12 |
13 |
14 |
15 | @else
16 |
17 | {{ __('Select Department') }}:
18 | @error($getStatePath())
{{ $message }}
@enderror
19 |
20 |
21 |
22 | @foreach($departments as $dept)
23 |
37 | @endforeach
38 |
39 | @endif
40 | @endif
41 |
42 |
43 |
--------------------------------------------------------------------------------
/resources/views/themes/zeus/wind/submitted.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ __('we received your message, and will get back to you shortly.') }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | hasPlugin('zeus-wind')) {
8 | Route::middleware(WindPlugin::get()->getMiddleware())
9 | ->prefix(WindPlugin::get()->getWindPrefix())
10 | ->get('{departmentSlug?}', ContactsForm::class)
11 | ->name('contact');
12 | }
13 |
--------------------------------------------------------------------------------
/src/Commands/PublishCommand.php:
--------------------------------------------------------------------------------
1 | callSilent('vendor:publish', ['--tag' => 'zeus-wind-migrations', '--force' => (bool) $this->option('force')]);
32 | $this->callSilent('vendor:publish', ['--tag' => 'zeus-wind-translations', '--force' => (bool) $this->option('force')]);
33 |
34 | // publish Zeus files
35 | $this->callSilent('vendor:publish', ['--tag' => 'zeus-config', '--force' => (bool) $this->option('force')]);
36 | $this->callSilent('vendor:publish', ['--tag' => 'zeus-views', '--force' => (bool) $this->option('force')]);
37 | $this->callSilent('vendor:publish', ['--tag' => 'zeus-assets', '--force' => (bool) $this->option('force')]);
38 |
39 | $this->output->success('Zeus and Wind has been Published successfully');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Configuration.php:
--------------------------------------------------------------------------------
1 | \LaraZeus\Wind\Models\Department::class,
36 | 'Letter' => \LaraZeus\Wind\Models\Letter::class,
37 | ];
38 |
39 | protected Closure | string $uploadDisk = 'public';
40 |
41 | protected Closure | string $uploadDirectory = 'logos';
42 |
43 | protected Closure | string $navigationGroupLabel = 'Wind';
44 |
45 | public function windPrefix(Closure | string $prefix): static
46 | {
47 | $this->windPrefix = $prefix;
48 |
49 | return $this;
50 | }
51 |
52 | public function getWindPrefix(): Closure | string
53 | {
54 | return $this->evaluate($this->windPrefix);
55 | }
56 |
57 | public function windMiddleware(array $middleware): static
58 | {
59 | $this->windMiddleware = $middleware;
60 |
61 | return $this;
62 | }
63 |
64 | public function getMiddleware(): array
65 | {
66 | return $this->windMiddleware;
67 | }
68 |
69 | public function defaultStatus(string $status): static
70 | {
71 | $this->defaultStatus = $status;
72 |
73 | return $this;
74 | }
75 |
76 | public function getDefaultStatus(): string
77 | {
78 | return $this->defaultStatus;
79 | }
80 |
81 | public function defaultDepartmentId(Closure | int $defaultDepartment): static
82 | {
83 | $this->defaultDepartmentId = $defaultDepartment;
84 |
85 | return $this;
86 | }
87 |
88 | public function getDefaultDepartmentId(): Closure | int
89 | {
90 | return $this->evaluate($this->defaultDepartmentId);
91 | }
92 |
93 | public function departmentResource(bool $condition = true): static
94 | {
95 | $this->hasDepartmentResource = $condition;
96 |
97 | return $this;
98 | }
99 |
100 | public function hasDepartmentResource(): bool
101 | {
102 | return $this->hasDepartmentResource;
103 | }
104 |
105 | public function uploadDisk(Closure | string $disk): static
106 | {
107 | $this->uploadDisk = $disk;
108 |
109 | return $this;
110 | }
111 |
112 | public function getUploadDisk(): Closure | string
113 | {
114 | return $this->evaluate($this->uploadDisk);
115 | }
116 |
117 | public function uploadDirectory(Closure | string $dir): static
118 | {
119 | $this->uploadDirectory = $dir;
120 |
121 | return $this;
122 | }
123 |
124 | public function getUploadDirectory(): Closure | string
125 | {
126 | return $this->evaluate($this->uploadDirectory);
127 | }
128 |
129 | public function navigationGroupLabel(Closure | string $lable): static
130 | {
131 | $this->navigationGroupLabel = $lable;
132 |
133 | return $this;
134 | }
135 |
136 | public function getNavigationGroupLabel(): Closure | string
137 | {
138 | return $this->evaluate($this->navigationGroupLabel);
139 | }
140 |
141 | public function windModels(array $models): static
142 | {
143 | $this->windModels = $models;
144 |
145 | return $this;
146 | }
147 |
148 | public function getWindModels(): array
149 | {
150 | return $this->windModels;
151 | }
152 |
153 | public static function getModel(string $model): string
154 | {
155 | return array_merge(
156 | (new static)->windModels,
157 | (new static)::get()->getWindModels()
158 | )[$model];
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Events/LetterSent.php:
--------------------------------------------------------------------------------
1 | letter = $letter;
28 | }
29 |
30 | /**
31 | * Get the channels the event should broadcast on.
32 | */
33 | public function broadcastOn(): Channel | PrivateChannel | array
34 | {
35 | return new PrivateChannel('zeus-wind');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Events/NewLetterSent.php:
--------------------------------------------------------------------------------
1 | letter = $letter;
28 | }
29 |
30 | /**
31 | * Get the channels the event should broadcast on.
32 | */
33 | public function broadcastOn(): Channel | PrivateChannel | array
34 | {
35 | return new PrivateChannel('zeus-wind');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Events/ReplySent.php:
--------------------------------------------------------------------------------
1 | letter = $letter;
28 | }
29 |
30 | /**
31 | * Get the channels the event should broadcast on.
32 | */
33 | public function broadcastOn(): Channel | PrivateChannel | array
34 | {
35 | return new PrivateChannel('zeus-wind');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Filament/Resources/DepartmentResource.php:
--------------------------------------------------------------------------------
1 | getModel('Department');
44 | }
45 |
46 | public static function getNavigationBadge(): ?string
47 | {
48 | return static::getModel()::count();
49 | }
50 |
51 | public static function form(Form $form): Form
52 | {
53 | return $form
54 | ->schema([
55 | TextInput::make('name')
56 | ->required()
57 | ->maxLength(255)
58 | ->live(onBlur: true)
59 | ->label(__('name'))
60 | ->afterStateUpdated(function (Set $set, $state, $context) {
61 | if ($context === 'edit') {
62 | return;
63 | }
64 |
65 | $set('slug', str()->slug($state));
66 | }),
67 |
68 | TextInput::make('slug')->required()->maxLength(255)->label(__('slug')),
69 | TextInput::make('ordering')->required()->numeric()->label(__('ordering')),
70 | Toggle::make('is_active')->label(__('is active'))->inline(false),
71 | Textarea::make('desc')->maxLength(65535)->columnSpan(['sm' => 2])->label(__('desc')),
72 |
73 | FileUpload::make('logo')
74 | ->disk(WindPlugin::get()->getUploadDisk())
75 | ->directory(WindPlugin::get()->getUploadDirectory())
76 | ->columnSpan(['sm' => 2])
77 | ->label(__('logo')),
78 | ]);
79 | }
80 |
81 | /**
82 | * @return Builder
83 | */
84 | public static function getEloquentQuery(): Builder
85 | {
86 | return parent::getEloquentQuery()
87 | ->withoutGlobalScopes([
88 | SoftDeletingScope::class,
89 | ]);
90 | }
91 |
92 | public static function table(Table $table): Table
93 | {
94 | return $table
95 | ->columns([
96 | TextColumn::make('name')
97 | ->label(__('name'))
98 | ->sortable()
99 | ->searchable()
100 | ->toggleable(),
101 | TextColumn::make('desc')
102 | ->searchable()
103 | ->label(__('desc'))
104 | ->toggleable(),
105 | TextColumn::make('ordering')
106 | ->searchable()
107 | ->sortable()
108 | ->label(__('ordering'))
109 | ->toggleable(),
110 | IconColumn::make('is_active')
111 | ->boolean()
112 | ->searchable()
113 | ->sortable()
114 | ->label(__('is active'))
115 | ->toggleable(),
116 | ImageColumn::make('logo')
117 | ->disk(WindPlugin::get()->getUploadDisk())
118 | ->label(__('logo'))
119 | ->toggleable(isToggledHiddenByDefault: true),
120 | ])
121 | ->defaultSort('id', 'desc')
122 | ->filters([
123 | TrashedFilter::make(),
124 | Filter::make('is_active')
125 | ->label(__('is active'))
126 | ->toggle()
127 | ->query(fn (Builder $query): Builder => $query->where('is_active', true)),
128 | Filter::make('not_active')
129 | ->label(__('not active'))
130 | ->toggle()
131 | ->query(fn (Builder $query): Builder => $query->where('is_active', false)),
132 | ])
133 | ->bulkActions([
134 | DeleteBulkAction::make(),
135 | ForceDeleteBulkAction::make(),
136 | RestoreBulkAction::make(),
137 | ])
138 | ->actions([
139 | ActionGroup::make([
140 | EditAction::make('edit')->label(__('Edit')),
141 | ViewAction::make('view')
142 | ->color('primary')
143 | ->label(__('View')),
144 | Action::make('Open')
145 | ->color('warning')
146 | ->icon('heroicon-o-arrow-top-right-on-square')
147 | ->label(__('Open'))
148 | ->url(fn (Model $record): string => route('contact', ['departmentSlug' => $record]))
149 | ->openUrlInNewTab(),
150 | DeleteAction::make('delete'),
151 | ForceDeleteAction::make(),
152 | RestoreAction::make(),
153 | ]),
154 | ]);
155 | }
156 |
157 | public static function getPages(): array
158 | {
159 | return [
160 | 'index' => Pages\ListDepartments::route('/'),
161 | 'create' => Pages\CreateDepartment::route('/create'),
162 | 'edit' => Pages\EditDepartment::route('/{record}/edit'),
163 | ];
164 | }
165 |
166 | public static function getLabel(): string
167 | {
168 | return __('Department');
169 | }
170 |
171 | public static function getPluralLabel(): string
172 | {
173 | return __('Departments');
174 | }
175 |
176 | public static function getNavigationLabel(): string
177 | {
178 | return __('Departments');
179 | }
180 |
181 | public static function getNavigationGroup(): ?string
182 | {
183 | return WindPlugin::get()->getNavigationGroupLabel();
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/Filament/Resources/DepartmentResource/Pages/CreateDepartment.php:
--------------------------------------------------------------------------------
1 | icon('heroicon-o-arrow-top-right-on-square')
18 | ->label(__('Open'))
19 | ->url(fn (): string => route('contact', ['departmentSlug' => $this->record]))
20 | ->openUrlInNewTab(),
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Filament/Resources/DepartmentResource/Pages/ListDepartments.php:
--------------------------------------------------------------------------------
1 | getModel('Letter');
43 | }
44 |
45 | public static function getNavigationBadge(): ?string
46 | {
47 | return static::getModel()::where('status', WindPlugin::get()->getDefaultStatus())->count();
48 | }
49 |
50 | /**
51 | * @return Builder
52 | */
53 | public static function getEloquentQuery(): Builder
54 | {
55 | return parent::getEloquentQuery()
56 | ->withoutGlobalScopes([
57 | SoftDeletingScope::class,
58 | ]);
59 | }
60 |
61 | public static function form(Form $form): Form
62 | {
63 | return $form
64 | ->schema([
65 | Section::make()
66 | ->visibleOn('edit')
67 | ->schema([
68 | Placeholder::make('sender_info')
69 | ->label('Sender Info:')
70 | ->columnSpan(['sm' => 2]),
71 |
72 | TextInput::make('name')
73 | ->label(__('name'))
74 | ->required()
75 | ->disabled()
76 | ->maxLength(255),
77 |
78 | TextInput::make('email')
79 | ->label(__('email'))
80 | ->email()
81 | ->required()
82 | ->disabled()
83 | ->maxLength(255),
84 |
85 | TextInput::make('title')
86 | ->label(__('title'))
87 | ->required()
88 | ->disabled()
89 | ->maxLength(255),
90 |
91 | TextInput::make('created_at')
92 | ->label(__('sent at'))
93 | ->disabled(),
94 |
95 | Placeholder::make('message')
96 | ->label(__('message'))
97 | ->disabled()
98 | ->content(fn (Letter $record) => new HtmlString($record->message))
99 | ->columnSpan(['sm' => 2]),
100 | ])
101 | ->columns(),
102 |
103 | Section::make()
104 | ->visibleOn('edit')
105 | ->schema([
106 | Select::make('department_id')
107 | ->label(__('department'))
108 | ->options(WindPlugin::get()->getModel('Department')::pluck('name', 'id'))
109 | ->required()
110 | ->visible(fn (): bool => WindPlugin::get()->hasDepartmentResource()),
111 |
112 | TextInput::make('status')
113 | ->label(__('status'))
114 | ->required()
115 | ->maxLength(255),
116 |
117 | TextInput::make('reply_title')
118 | ->label(__('reply_title'))
119 | ->required()
120 | ->maxLength(255)
121 | ->columnSpan(['sm' => 2]),
122 |
123 | RichEditor::make('reply_message')
124 | ->label(__('reply_message'))
125 | ->required()
126 | ->maxLength(65535)
127 | ->columnSpan(['sm' => 2]),
128 | ])
129 | ->columns(),
130 |
131 | Section::make()
132 | ->visibleOn('create')
133 | ->schema([
134 | TextInput::make('name')
135 | ->label(__('to name'))
136 | ->required()
137 | ->maxLength(255),
138 |
139 | TextInput::make('email')
140 | ->label(__('to email'))
141 | ->email()
142 | ->required()
143 | ->maxLength(255),
144 |
145 | TextInput::make('title')
146 | ->label(__('title'))
147 | ->required()
148 | ->maxLength(255),
149 |
150 | Select::make('department_id')
151 | ->label(__('department'))
152 | ->options(WindPlugin::get()->getModel('Department')::pluck('name', 'id'))
153 | ->required(fn (): bool => WindPlugin::get()->hasDepartmentResource())
154 | ->visible(fn (): bool => WindPlugin::get()->hasDepartmentResource()),
155 |
156 | RichEditor::make('message')
157 | ->label(__('message'))
158 | ->required()
159 | ->maxLength(65535)
160 | ->columnSpan(['sm' => 2]),
161 | ])
162 | ->columns(),
163 | ]);
164 | }
165 |
166 | public static function table(Table $table): Table
167 | {
168 | return $table
169 | ->columns([
170 | Split::make([
171 | ImageColumn::make('avatar')
172 | ->getStateUsing(fn (
173 | $record
174 | ) => 'https://ui-avatars.com/api/?name=' . urlencode($record->name) . '&color=FFFFFF&background=111827')
175 | ->toggleable()
176 | ->circular()
177 | ->grow(false),
178 | Stack::make([
179 | TextColumn::make('name')
180 | ->weight('bold')
181 | ->toggleable()
182 | ->searchable()
183 | ->limit(20)
184 | ->sortable(),
185 | TextColumn::make('email')
186 | ->limit(20),
187 | ]),
188 | Stack::make([
189 | TextColumn::make('title')
190 | ->sortable()
191 | ->searchable()
192 | ->toggleable()
193 | ->label(__('title')),
194 | TextColumn::make('department.name')
195 | ->sortable()
196 | ->badge()
197 | ->searchable()
198 | ->toggleable()
199 | ->visible(fn (): bool => WindPlugin::get()->hasDepartmentResource())
200 | ->label(__('department')),
201 | ]),
202 |
203 | Stack::make([
204 | TextColumn::make('created_at')
205 | ->sortable()
206 | ->searchable()
207 | ->toggleable()
208 | ->dateTime()
209 | ->label(__('sent at')),
210 | TextColumn::make('status')
211 | ->formatStateUsing(fn (string $state): string => __("status_{$state}"))
212 | ->label(__('status'))
213 | ->sortable()
214 | ->searchable()
215 | ->toggleable()
216 | ->badge()
217 | ->color(fn (string $state): string => match ($state) {
218 | 'NEW' => 'danger',
219 | 'REPLIED' => 'gray',
220 | 'READ' => 'success',
221 | default => '',
222 | }),
223 | ]),
224 | ]),
225 | ])
226 | ->recordClasses(fn (Letter $record) => match ($record->status) {
227 | 'NEW' => 'border-s-2 border-danger-600 dark:border-danger-300',
228 | 'REPLIED' => 'border-s-2 border-gray-600 dark:border-gray-300',
229 | 'READ' => 'border-s-2 border-success-600 dark:border-success-300',
230 | default => '',
231 | })
232 | ->defaultSort('id', 'desc')
233 | ->bulkActions([
234 | DeleteBulkAction::make(),
235 | ForceDeleteBulkAction::make(),
236 | RestoreBulkAction::make(),
237 | ])
238 | ->filters([
239 | TrashedFilter::make(),
240 | SelectFilter::make('status')
241 | ->options([
242 | 'NEW' => __('NEW'),
243 | 'READ' => __('READ'),
244 | 'REPLIED' => __('REPLIED'),
245 | ])
246 | ->label(__('status')),
247 | SelectFilter::make('department_id')
248 | ->visible(fn (): bool => WindPlugin::get()->hasDepartmentResource())
249 | ->options(WindPlugin::get()->getModel('Department')::pluck('name', 'id'))
250 | ->label(__('department')),
251 | ])
252 | ->actions([
253 | ActionGroup::make([
254 | EditAction::make('edit')->label(__('Edit')),
255 | DeleteAction::make('delete'),
256 | ForceDeleteAction::make(),
257 | RestoreAction::make(),
258 | ]),
259 | ]);
260 | }
261 |
262 | public static function getPages(): array
263 | {
264 | return [
265 | 'index' => Pages\ListLetters::route('/'),
266 | 'create' => Pages\CreateLetter::route('/create'),
267 | 'edit' => Pages\EditLetter::route('/{record}/edit'),
268 | ];
269 | }
270 |
271 | public static function getLabel(): string
272 | {
273 | return __('Letter');
274 | }
275 |
276 | public static function getPluralLabel(): string
277 | {
278 | return __('Letters');
279 | }
280 |
281 | public static function getNavigationLabel(): string
282 | {
283 | return __('Letters');
284 | }
285 |
286 | public static function getNavigationGroup(): ?string
287 | {
288 | return WindPlugin::get()->getNavigationGroupLabel();
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/Filament/Resources/LetterResource/Pages/CreateLetter.php:
--------------------------------------------------------------------------------
1 | record);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Filament/Resources/LetterResource/Pages/EditLetter.php:
--------------------------------------------------------------------------------
1 | record->reply_message === null && strtoupper($this->record->status) === WindPlugin::get()->getDefaultStatus()) {
22 | $this->record->update(['status' => 'READ']);
23 | }
24 | }
25 |
26 | protected function afterSave(): void
27 | {
28 | if ($this->record->reply_message !== null) {
29 | $this->record->update(['status' => 'REPLIED']);
30 | ReplySent::dispatch($this->record);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Filament/Resources/LetterResource/Pages/ListLetters.php:
--------------------------------------------------------------------------------
1 | hasDepartmentResource()) {
45 | if ($departmentSlug !== null) {
46 | $this->department = WindPlugin::get()->getModel('Department')::where('slug', $departmentSlug)->first();
47 | } elseif (WindPlugin::get()->getDefaultDepartmentId() !== null) {
48 | $this->department = WindPlugin::get()->getModel('Department')::find(WindPlugin::get()->getDefaultDepartmentId());
49 | }
50 | }
51 |
52 | $this->status = WindPlugin::get()->getDefaultStatus();
53 |
54 | $this->form->fill(
55 | [
56 | 'department_id' => $this->department->id ?? null,
57 | 'status' => WindPlugin::get()->getDefaultStatus(),
58 | ]
59 | );
60 | }
61 |
62 | public function store(): void
63 | {
64 | $letter = WindPlugin::get()->getModel('Letter')::create($this->form->getState());
65 | $this->sent = true;
66 | LetterSent::dispatch($letter);
67 | }
68 |
69 | protected function getFormSchema(): array
70 | {
71 | return [
72 | Grid::make()
73 | ->schema([
74 | ViewField::make('department_id')
75 | ->view(app('windTheme') . '.departments')
76 | ->columnSpan([
77 | 'default' => 1,
78 | 'md' => 2,
79 | ])
80 | ->hiddenLabel()
81 | ->visible(fn (): bool => WindPlugin::get()->hasDepartmentResource()),
82 |
83 | Section::make()
84 | ->schema([
85 | Grid::make()
86 | ->schema([
87 | TextInput::make('name')
88 | ->required()
89 | ->minLength(6)
90 | ->label(__('name')),
91 |
92 | TextInput::make('email')
93 | ->required()
94 | ->email()
95 | ->label(__('email')),
96 | ]),
97 |
98 | TextInput::make('title')
99 | ->required()
100 | ->label(__('title')),
101 |
102 | Textarea::make('message')
103 | ->rows(10)
104 | ->required()
105 | ->label(__('message')),
106 | ]),
107 | ]),
108 | ];
109 | }
110 |
111 | public function render(): View | Application | Factory | \Illuminate\Contracts\Foundation\Application
112 | {
113 | seo()
114 | ->site(config('zeus.site_title', 'Laravel'))
115 | ->title(__('Contact Us') . ' - ' . config('zeus.site_title'))
116 | ->description(__('Contact Us') . ' - ' . config('zeus.site_description') . ' ' . config('zeus.site_title'))
117 | ->rawTag('favicon', '')
118 | ->rawTag('')
119 | ->withUrl()
120 | ->twitter();
121 |
122 | return view(app('windTheme') . '.contact-form')
123 | ->layout(config('zeus.layout'));
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Models/Department.php:
--------------------------------------------------------------------------------
1 | 'datetime',
36 | 'end_date' => 'datetime',
37 | 'options' => 'array',
38 | 'is_active' => 'boolean',
39 | ];
40 |
41 | public function getRouteKeyName(): string
42 | {
43 | return 'slug';
44 | }
45 |
46 | protected static function newFactory(): DepartmentFactory
47 | {
48 | return DepartmentFactory::new();
49 | }
50 |
51 | /** @phpstan-return hasMany */
52 | public function letters(): HasMany
53 | {
54 | return $this->hasMany(WindPlugin::get()->getModel('Department'));
55 | }
56 |
57 | public function image(): ?string
58 | {
59 | if (str($this->logo)->startsWith('http')) {
60 | return $this->logo;
61 | }
62 | if ($this->logo !== null) {
63 | return Storage::disk(WindPlugin::get()->getUploadDisk())
64 | ->url($this->logo);
65 | }
66 |
67 | return null;
68 | }
69 |
70 | public function scopeDepartments(Builder $query): void
71 | {
72 | $query->where('is_active', true)->orderBy('ordering');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Models/Letter.php:
--------------------------------------------------------------------------------
1 | */
35 | public function department(): BelongsTo
36 | {
37 | return $this->belongsTo(WindPlugin::get()->getModel('Department'));
38 | }
39 |
40 | public function getReplyTitleAttribute(): string
41 | {
42 | return $this->reply_title ?? __('re') . ': ' . $this->title;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/WindPlugin.php:
--------------------------------------------------------------------------------
1 | hasDepartmentResource()) {
24 | $panel->resources([
25 | DepartmentResource::class,
26 | ]);
27 | }
28 |
29 | $panel
30 | ->resources([
31 | LetterResource::class,
32 | ]);
33 | }
34 |
35 | public static function make(): static
36 | {
37 | return new self;
38 | }
39 |
40 | public static function get(): static
41 | {
42 | // @phpstan-ignore-next-line
43 | return filament('zeus-wind');
44 | }
45 |
46 | public function boot(Panel $panel): void
47 | {
48 | //
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/WindServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
23 | $this->publishes([
24 | __DIR__ . '/../database/seeders' => database_path('seeders'),
25 | ], 'zeus-wind-seeder');
26 |
27 | $this->publishes([
28 | __DIR__ . '/../database/factories' => database_path('factories'),
29 | ], 'zeus-wind-factories');
30 | }
31 | }
32 |
33 | public function configurePackage(Package $package): void
34 | {
35 | $package
36 | ->name(static::$name)
37 | ->hasTranslations()
38 | ->hasMigrations($this->getMigrations())
39 | ->hasRoute('web')
40 | ->hasViews('zeus')
41 | ->hasCommands($this->getCommands());
42 | }
43 |
44 | /**
45 | * @return array
46 | */
47 | protected function getMigrations(): array
48 | {
49 | return [
50 | 'create_department_table',
51 | 'create_letters_table',
52 | ];
53 | }
54 |
55 | /**
56 | * @return array
57 | */
58 | protected function getCommands(): array
59 | {
60 | return [
61 | PublishCommand::class,
62 | ];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/AdminPanelProvider.php:
--------------------------------------------------------------------------------
1 | default()
26 | ->id('admin')
27 | ->login()
28 | ->plugins([
29 | WindPlugin::make(),
30 | SpatieLaravelTranslatablePlugin::make()
31 | ->defaultLocales(['en']),
32 | ])
33 | ->registration()
34 | ->passwordReset()
35 | ->emailVerification()
36 | ->middleware([
37 | EncryptCookies::class,
38 | AddQueuedCookiesToResponse::class,
39 | StartSession::class,
40 | AuthenticateSession::class,
41 | ShareErrorsFromSession::class,
42 | VerifyCsrfToken::class,
43 | SubstituteBindings::class,
44 | DisableBladeIconComponents::class,
45 | DispatchServingFilamentEvent::class,
46 | ])
47 | ->authMiddleware([
48 | Authenticate::class,
49 | ]);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/ExampleTest.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in(__DIR__);
6 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | 'LaraZeus\Wind\\Database\\Factories\\' . class_basename($modelName) . 'Factory'
32 | );
33 | }
34 |
35 | protected function getPackageProviders($app)
36 | {
37 | return [
38 | ActionsServiceProvider::class,
39 | BladeCaptureDirectiveServiceProvider::class,
40 | BladeHeroiconsServiceProvider::class,
41 | BladeIconsServiceProvider::class,
42 | FilamentServiceProvider::class,
43 | FormsServiceProvider::class,
44 | InfolistsServiceProvider::class,
45 | LivewireServiceProvider::class,
46 | NotificationsServiceProvider::class,
47 | SpatieLaravelTranslatablePluginServiceProvider::class,
48 | SupportServiceProvider::class,
49 | TablesServiceProvider::class,
50 | WidgetsServiceProvider::class,
51 | AdminPanelProvider::class,
52 | CoreServiceProvider::class,
53 | WindServiceProvider::class,
54 | SEOServiceProvider::class,
55 | ];
56 | }
57 |
58 | public function getEnvironmentSetUp($app)
59 | {
60 | config()->set('database.default', 'testing');
61 |
62 | /*
63 | $migration = include __DIR__.'/../database/migrations/create_Wind_table.php.stub';
64 | $migration->up();
65 | */
66 | }
67 | }
68 |
--------------------------------------------------------------------------------