From a7cde979b1409e3fe81c983163f1b7efec2c7459 Mon Sep 17 00:00:00 2001 From: Ned Zimmerman Date: Mon, 25 Nov 2024 09:26:01 -0400 Subject: [PATCH 01/16] feat: migrate components from fluid-project/hearth --- .editorconfig | 15 ++ .gitattributes | 20 ++ .github/ISSUE_TEMPLATE/bug.yml | 66 ++++++ .github/ISSUE_TEMPLATE/config.yml | 11 + .../workflows/fix-php-code-style-issues.yml | 28 +++ .github/workflows/phpstan.yml | 28 +++ .github/workflows/release-please.yml | 14 ++ .github/workflows/run-tests.yml | 55 +++++ .gitignore | 43 ++-- .release-please-manifest.json | 3 + LICENSE.md | 21 ++ README.md | 85 ++++++- composer.json | 93 ++++++++ phpstan-baseline.neon | 0 phpstan.neon.dist | 12 + phpunit.xml.dist | 31 +++ release-please-config.json | 14 ++ renovate.json | 8 + resources/lang/en/alert.php | 8 + resources/lang/en/auth.php | 48 ++++ resources/lang/en/dashboard.php | 6 + resources/lang/en/errors.php | 31 +++ resources/lang/en/forms.php | 8 + resources/lang/en/mail.php | 8 + resources/lang/en/nav.php | 5 + resources/lang/en/passwords.php | 22 ++ resources/lang/en/routes.php | 8 + resources/lang/en/user.php | 24 ++ resources/lang/en/validation.php | 14 ++ resources/lang/en/welcome.php | 6 + resources/lang/fr/alert.php | 8 + resources/lang/fr/auth.php | 48 ++++ resources/lang/fr/dashboard.php | 6 + resources/lang/fr/errors.php | 31 +++ resources/lang/fr/forms.php | 8 + resources/lang/fr/mail.php | 8 + resources/lang/fr/nav.php | 5 + resources/lang/fr/passwords.php | 22 ++ resources/lang/fr/routes.php | 8 + resources/lang/fr/user.php | 24 ++ resources/lang/fr/validation.php | 14 ++ resources/lang/fr/welcome.php | 6 + resources/views/alert.blade.php | 4 + resources/views/checkbox.blade.php | 13 ++ resources/views/checkboxes.blade.php | 11 + resources/views/date-input.blade.php | 30 +++ resources/views/error.blade.php | 9 + resources/views/hint.blade.php | 1 + resources/views/input.blade.php | 11 + resources/views/label.blade.php | 3 + resources/views/language-switcher.blade.php | 13 ++ resources/views/locale-select.blade.php | 18 ++ .../views/password-confirmation.blade.php | 19 ++ resources/views/radio-button.blade.php | 12 + resources/views/radio-buttons.blade.php | 11 + resources/views/select.blade.php | 15 ++ resources/views/textarea.blade.php | 11 + resources/views/translatable-input.blade.php | 21 ++ .../views/translatable-textarea.blade.php | 21 ++ src/Components/Alert.php | 43 ++++ src/Components/Checkbox.php | 109 +++++++++ src/Components/Checkboxes.php | 73 ++++++ src/Components/DateInput.php | 177 +++++++++++++++ src/Components/Error.php | 52 +++++ src/Components/Hint.php | 35 +++ src/Components/Input.php | 102 +++++++++ src/Components/Label.php | 35 +++ src/Components/LanguageSwitcher.php | 51 +++++ src/Components/LocaleSelect.php | 131 +++++++++++ src/Components/PasswordConfirmation.php | 52 +++++ src/Components/RadioButton.php | 110 +++++++++ src/Components/RadioButtons.php | 79 +++++++ src/Components/Select.php | 121 ++++++++++ src/Components/Textarea.php | 112 ++++++++++ src/Components/TranslatableInput.php | 53 +++++ src/Components/TranslatableTextarea.php | 53 +++++ src/Facades/HearthComponents.php | 16 ++ src/HearthComponents.php | 5 + src/HearthComponentsServiceProvider.php | 62 ++++++ src/Traits/AriaDescribable.php | 24 ++ .../GeneratesTranslatableFieldAttributes.php | 42 ++++ src/Traits/HandlesValidation.php | 33 +++ src/helpers.php | 26 +++ stubs/lang/en/auth.php | 20 ++ stubs/lang/en/forms.php | 26 +++ stubs/lang/en/invitation.php | 20 ++ stubs/lang/en/membership.php | 8 + stubs/lang/en/organization.php | 32 +++ stubs/lang/en/pagination.php | 19 ++ stubs/lang/en/passwords.php | 22 ++ stubs/lang/en/resource-collection.php | 19 ++ stubs/lang/en/resource-select.php | 12 + stubs/lang/en/resource.php | 17 ++ stubs/lang/en/roles.php | 6 + stubs/lang/en/routes.php | 9 + stubs/lang/en/validation.php | 162 ++++++++++++++ stubs/lang/fr/auth.php | 20 ++ stubs/lang/fr/forms.php | 26 +++ stubs/lang/fr/invitation.php | 20 ++ stubs/lang/fr/membership.php | 8 + stubs/lang/fr/organization.php | 32 +++ stubs/lang/fr/pagination.php | 19 ++ stubs/lang/fr/passwords.php | 22 ++ stubs/lang/fr/resource-collection.php | 19 ++ stubs/lang/fr/resource-select.php | 12 + stubs/lang/fr/resource.php | 17 ++ stubs/lang/fr/roles.php | 6 + stubs/lang/fr/routes.php | 9 + stubs/lang/fr/validation.php | 162 ++++++++++++++ tests/ArchTest.php | 5 + tests/Components/CheckboxTest.php | 65 ++++++ tests/Components/CheckboxesTest.php | 208 +++++++++++++++++ tests/Components/DateInputTest.php | 47 ++++ tests/Components/HintTest.php | 14 ++ tests/Components/InputTest.php | 56 +++++ tests/Components/RadioButtonTest.php | 70 ++++++ tests/Components/RadioButtonsTest.php | 209 ++++++++++++++++++ tests/Components/SelectTest.php | 49 ++++ tests/Components/TextareaTest.php | 45 ++++ tests/Components/TranslatableInputTest.php | 45 ++++ tests/Components/TranslatableTextareaTest.php | 45 ++++ tests/Fixtures/TranslatableModel.php | 51 +++++ tests/Pest.php | 5 + tests/TestCase.php | 40 ++++ tests/Unit/HelpersTest.php | 15 ++ .../Providers/WorkbenchServiceProvider.php | 25 +++ 126 files changed, 4462 insertions(+), 18 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/workflows/fix-php-code-style-issues.yml create mode 100644 .github/workflows/phpstan.yml create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/run-tests.yml create mode 100644 .release-please-manifest.json create mode 100644 LICENSE.md create mode 100644 composer.json create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon.dist create mode 100644 phpunit.xml.dist create mode 100644 release-please-config.json create mode 100644 renovate.json create mode 100644 resources/lang/en/alert.php create mode 100644 resources/lang/en/auth.php create mode 100644 resources/lang/en/dashboard.php create mode 100644 resources/lang/en/errors.php create mode 100644 resources/lang/en/forms.php create mode 100644 resources/lang/en/mail.php create mode 100644 resources/lang/en/nav.php create mode 100644 resources/lang/en/passwords.php create mode 100644 resources/lang/en/routes.php create mode 100644 resources/lang/en/user.php create mode 100644 resources/lang/en/validation.php create mode 100644 resources/lang/en/welcome.php create mode 100644 resources/lang/fr/alert.php create mode 100644 resources/lang/fr/auth.php create mode 100644 resources/lang/fr/dashboard.php create mode 100644 resources/lang/fr/errors.php create mode 100644 resources/lang/fr/forms.php create mode 100644 resources/lang/fr/mail.php create mode 100644 resources/lang/fr/nav.php create mode 100644 resources/lang/fr/passwords.php create mode 100644 resources/lang/fr/routes.php create mode 100644 resources/lang/fr/user.php create mode 100644 resources/lang/fr/validation.php create mode 100644 resources/lang/fr/welcome.php create mode 100644 resources/views/alert.blade.php create mode 100644 resources/views/checkbox.blade.php create mode 100644 resources/views/checkboxes.blade.php create mode 100644 resources/views/date-input.blade.php create mode 100644 resources/views/error.blade.php create mode 100644 resources/views/hint.blade.php create mode 100644 resources/views/input.blade.php create mode 100644 resources/views/label.blade.php create mode 100644 resources/views/language-switcher.blade.php create mode 100644 resources/views/locale-select.blade.php create mode 100644 resources/views/password-confirmation.blade.php create mode 100644 resources/views/radio-button.blade.php create mode 100644 resources/views/radio-buttons.blade.php create mode 100644 resources/views/select.blade.php create mode 100644 resources/views/textarea.blade.php create mode 100644 resources/views/translatable-input.blade.php create mode 100644 resources/views/translatable-textarea.blade.php create mode 100644 src/Components/Alert.php create mode 100644 src/Components/Checkbox.php create mode 100644 src/Components/Checkboxes.php create mode 100644 src/Components/DateInput.php create mode 100644 src/Components/Error.php create mode 100644 src/Components/Hint.php create mode 100644 src/Components/Input.php create mode 100644 src/Components/Label.php create mode 100644 src/Components/LanguageSwitcher.php create mode 100644 src/Components/LocaleSelect.php create mode 100644 src/Components/PasswordConfirmation.php create mode 100644 src/Components/RadioButton.php create mode 100644 src/Components/RadioButtons.php create mode 100644 src/Components/Select.php create mode 100644 src/Components/Textarea.php create mode 100644 src/Components/TranslatableInput.php create mode 100644 src/Components/TranslatableTextarea.php create mode 100644 src/Facades/HearthComponents.php create mode 100755 src/HearthComponents.php create mode 100644 src/HearthComponentsServiceProvider.php create mode 100644 src/Traits/AriaDescribable.php create mode 100644 src/Traits/GeneratesTranslatableFieldAttributes.php create mode 100644 src/Traits/HandlesValidation.php create mode 100644 src/helpers.php create mode 100644 stubs/lang/en/auth.php create mode 100644 stubs/lang/en/forms.php create mode 100644 stubs/lang/en/invitation.php create mode 100644 stubs/lang/en/membership.php create mode 100644 stubs/lang/en/organization.php create mode 100644 stubs/lang/en/pagination.php create mode 100644 stubs/lang/en/passwords.php create mode 100644 stubs/lang/en/resource-collection.php create mode 100644 stubs/lang/en/resource-select.php create mode 100644 stubs/lang/en/resource.php create mode 100644 stubs/lang/en/roles.php create mode 100644 stubs/lang/en/routes.php create mode 100644 stubs/lang/en/validation.php create mode 100644 stubs/lang/fr/auth.php create mode 100644 stubs/lang/fr/forms.php create mode 100644 stubs/lang/fr/invitation.php create mode 100644 stubs/lang/fr/membership.php create mode 100644 stubs/lang/fr/organization.php create mode 100644 stubs/lang/fr/pagination.php create mode 100644 stubs/lang/fr/passwords.php create mode 100644 stubs/lang/fr/resource-collection.php create mode 100644 stubs/lang/fr/resource-select.php create mode 100644 stubs/lang/fr/resource.php create mode 100644 stubs/lang/fr/roles.php create mode 100644 stubs/lang/fr/routes.php create mode 100644 stubs/lang/fr/validation.php create mode 100644 tests/ArchTest.php create mode 100644 tests/Components/CheckboxTest.php create mode 100644 tests/Components/CheckboxesTest.php create mode 100644 tests/Components/DateInputTest.php create mode 100644 tests/Components/HintTest.php create mode 100644 tests/Components/InputTest.php create mode 100644 tests/Components/RadioButtonTest.php create mode 100644 tests/Components/RadioButtonsTest.php create mode 100644 tests/Components/SelectTest.php create mode 100644 tests/Components/TextareaTest.php create mode 100644 tests/Components/TranslatableInputTest.php create mode 100644 tests/Components/TranslatableTextareaTest.php create mode 100644 tests/Fixtures/TranslatableModel.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/HelpersTest.php create mode 100644 workbench/app/Providers/WorkbenchServiceProvider.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dd9a2b5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c09f81e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/phpunit.xml.dist export-ignore +/art export-ignore +/docs export-ignore +/tests export-ignore +/workbench export-ignore +/.editorconfig export-ignore +/.php_cs.dist.php export-ignore +/psalm.xml export-ignore +/psalm.xml.dist export-ignore +/testbench.yaml export-ignore +/UPGRADING.md export-ignore +/phpstan.neon.dist export-ignore +/phpstan-baseline.neon export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..fe4cfe6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,66 @@ +name: Bug Report +description: Report an Issue or Bug with the Package +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + We're sorry to hear you have a problem. Can you help us solve it by providing the following details. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: What did you expect to happen? + placeholder: I cannot currently do X thing because when I do, it breaks X thing. + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: How to reproduce the bug + description: How did this occur, please add any config values used and provide a set of reliable steps if possible. + placeholder: When I do X I see Y. + validations: + required: true + - type: input + id: package-version + attributes: + label: Package Version + description: What version of our Package are you running? Please be as specific as possible + placeholder: 2.0.0 + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP Version + description: What version of PHP are you running? Please be as specific as possible + placeholder: 8.2.0 + validations: + required: true + - type: input + id: laravel-version + attributes: + label: Laravel Version + description: What version of Laravel are you running? Please be as specific as possible + placeholder: 9.0.0 + validations: + required: true + - type: dropdown + id: operating-systems + attributes: + label: Which operating systems does with happen with? + description: You may select more than one. + multiple: true + options: + - macOS + - Windows + - Linux + - type: textarea + id: notes + attributes: + label: Notes + description: Use this field to provide any other notes that you feel might be relevant to the issue. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6474295 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/:vendor_slug/:package_name/discussions/new?category=q-a + about: Ask the community for help + - name: Request a feature + url: https://github.com/:vendor_slug/:package_name/discussions/new?category=ideas + about: Share ideas for new features + - name: Report a security issue + url: https://github.com/:vendor_slug/:package_name/security/policy + about: Learn how to notify us for sensitive bugs diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml new file mode 100644 index 0000000..56d54d3 --- /dev/null +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -0,0 +1,28 @@ +name: Fix PHP code style issues + +on: + push: + paths: + - '**.php' + +permissions: + contents: write + +jobs: + php-code-styling: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Fix PHP code style issues + uses: aglipanci/laravel-pint-action@2.4 + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: Fix styling diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..d5db2f1 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,28 @@ +name: PHPStan + +on: + push: + paths: + - '**.php' + - 'phpstan.neon.dist' + - '.github/workflows/phpstan.yml' + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + + - name: Install composer dependencies + uses: ramsey/composer-install@v3 + + - name: Run PHPStan + run: ./vendor/bin/phpstan --error-format=github diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..dbcbd7c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,14 @@ +name: release-please + +on: + push: + branches: + - main + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..ec0e6f0 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,55 @@ +name: run-tests + +on: + push: + paths: + - '**.php' + - '.github/workflows/run-tests.yml' + - 'phpunit.xml.dist' + - 'composer.json' + - 'composer.lock' + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest] + php: [8.3, 8.2] + laravel: [11.*] + stability: [prefer-lowest, prefer-stable] + include: + - laravel: 11.* + testbench: 9.* + carbon: ^2.63 + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Setup problem matchers + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.os == 'windows-latest' && '^^^' || '' }}${{ matrix.carbon }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + + - name: List Installed Dependencies + run: composer show -D + + - name: Execute tests + run: vendor/bin/pest --ci diff --git a/.gitignore b/.gitignore index 297959a..b60507f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,32 @@ -/vendor/ -node_modules/ +# Composer Related +composer.lock +/vendor + +# Frontend Assets +/node_modules + +# Logs npm-debug.log yarn-error.log -# Laravel 4 specific -bootstrap/compiled.php -app/storage/ +# Caches +.phpunit.cache +.phpunit.result.cache +/build -# Laravel 5 & Lumen specific -public/storage -public/hot +# IDE Helper +_ide_helper.php +_ide_helper_models.php +.phpstorm.meta.php -# Laravel 5 & Lumen specific with changed public path -public_html/storage -public_html/hot +# Editors +/.idea +/.fleet +/.vscode -storage/*.key -.env -Homestead.yaml -Homestead.json -/.vagrant -.phpunit.result.cache +# Misc +phpunit.xml +phpstan.neon +testbench.yaml +/docs +/coverage diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..12ef0bf --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "4.1.0" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f152948 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Hearth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 6860774..b3d53a4 100644 --- a/README.md +++ b/README.md @@ -1 +1,84 @@ -# hearth-components \ No newline at end of file +# This is my package hearth-components + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/fluid-project/hearth-components.svg?style=flat-square)](https://packagist.org/packages/fluid-project/hearth-components) +[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/fluid-project/hearth-components/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/fluid-project/hearth-components/actions?query=workflow%3Arun-tests+branch%3Amain) +[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/fluid-project/hearth-components/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/fluid-project/hearth-components/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) +[![Total Downloads](https://img.shields.io/packagist/dt/fluid-project/hearth-components.svg?style=flat-square)](https://packagist.org/packages/fluid-project/hearth-components) + +This is where your description should go. Limit it to a paragraph or two. Consider adding a small example. + +## Support us + +[](https://spatie.be/github-ad-click/hearth-components) + +We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). + +We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). + +## Installation + +You can install the package via composer: + +```bash +composer require fluid-project/hearth-components +``` + +You can publish and run the migrations with: + +```bash +php artisan vendor:publish --tag="hearth-components-migrations" +php artisan migrate +``` + +You can publish the config file with: + +```bash +php artisan vendor:publish --tag="hearth-components-config" +``` + +This is the contents of the published config file: + +```php +return [ +]; +``` + +Optionally, you can publish the views using + +```bash +php artisan vendor:publish --tag="hearth-components-views" +``` + +## Usage + +```php +$hearth = new Hearth\Hearth(); +echo $hearth->echoPhrase('Hello, Hearth!'); +``` + +## Testing + +```bash +composer test +``` + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Security Vulnerabilities + +Please review [our security policy](../../security/policy) on how to report security vulnerabilities. + +## Credits + +- [OCAD University](https://github.com/fluid-project) +- [All Contributors](../../contributors) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8339d47 --- /dev/null +++ b/composer.json @@ -0,0 +1,93 @@ +{ + "name": "fluid-project/hearth-components", + "description": "This is my package hearth-components", + "keywords": [ + "Hearth", + "laravel", + "hearth-components" + ], + "homepage": "https://github.com/fluid-project/hearth-components", + "license": "MIT", + "authors": [ + { + "name": "OCAD University", + "email": "idrc@ocadu.ca", + "role": "Developer" + } + ], + "require": { + "php": "^8.2|^8.3", + "chinleung/laravel-locales": "^2.1", + "codezero/laravel-unique-translation": "^4.3", + "commerceguys/intl": "^2.0", + "illuminate/contracts": "^11.0", + "livewire/livewire": "^3.5", + "spatie/laravel-package-tools": "^1.16", + "spatie/laravel-sluggable": "^3.6", + "spatie/laravel-translatable": "^6.8" + }, + "require-dev": { + "laravel/pint": "^1.14", + "nunomaduro/collision": "^8.1.1", + "larastan/larastan": "^2.9", + "orchestra/testbench": "^9.0.0", + "pestphp/pest": "^2.34", + "pestphp/pest-plugin-arch": "^2.7", + "pestphp/pest-plugin-laravel": "^2.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "spatie/laravel-ray": "^1.35" + }, + "autoload": { + "psr-4": { + "HearthComponents\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "autoload-dev": { + "psr-4": { + "HearthComponents\\Tests\\": "tests", + "Workbench\\App\\": "workbench/app/" + } + }, + "scripts": { + "post-autoload-dump": "@composer run prepare", + "clear": "@php vendor/bin/testbench package:purge-hearth-components --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": [ + "@composer run prepare", + "@php vendor/bin/testbench workbench:build --ansi" + ], + "start": [ + "Composer\\Config::disableProcessTimeout", + "@composer run build", + "@php vendor/bin/testbench serve" + ], + "analyse": "vendor/bin/phpstan analyse", + "test": "vendor/bin/pest", + "test-coverage": "vendor/bin/pest --coverage", + "format": "vendor/bin/pint" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true + } + }, + "extra": { + "laravel": { + "providers": [ + "HearthComponents\\HearthComponentsServiceProvider" + ], + "aliases": { + "HearthComponents": "HearthComponents\\Facades\\HearthComponents" + } + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..ab1b4c3 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,12 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 5 + paths: + - src + - config + - database + tmpDir: build/phpstan + checkOctaneCompatibility: true + checkModelProperties: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..094519a --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + tests + + + + + + + + ./src + + + diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..b33b082 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,14 @@ +{ + "changelog-path": "CHANGELOG.md", + "release-type": "php", + "bump-minor-pre-major": false, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "packages": { + ".": { + "include-component-in-tag": false + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f0416a7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "lockFileMaintenance": { + "enabled": true, + "automerge": true + } +} diff --git a/resources/lang/en/alert.php b/resources/lang/en/alert.php new file mode 100644 index 0000000..a45f1ce --- /dev/null +++ b/resources/lang/en/alert.php @@ -0,0 +1,8 @@ + 'Error', + 'warning' => 'Warning', + 'success' => 'Success', + 'notice' => 'Notice', +]; diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php new file mode 100644 index 0000000..8ea11d0 --- /dev/null +++ b/resources/lang/en/auth.php @@ -0,0 +1,48 @@ + 'Sign in', + 'sign_out' => 'Sign out', + 'create_account' => 'Create an account', + 'label_password' => 'Password', + 'label_password_confirmation' => 'Confirm password', + 'existing_account_prompt' => 'Do you already have an account?', + 'create_your_account' => 'Create your account', + 'label_current_password' => 'Current password', + 'change_password' => 'Change password', + 'action_cancel' => 'Cancel', + 'action_confirm' => 'Confirm', + 'password_change_succeeded' => 'Your password has been changed.', + 'label_remember_me' => 'Remember me', + 'error_intro' => 'Sorry, something went wrong.', + 'wrong_password' => 'The provided password is incorrect.', + 'confirm_intro' => 'For your security, please confirm your password before continuing.', + 'forget_prompt' => 'Forgot your password?', + 'forgot_intro' => 'Forgot your password? Enter your email address and we will email you a password reset link that will let you choose a new one.', + 'forgot_submit' => 'Send password reset link', + 'reset_submit' => 'Reset password', + 'verification_required' => 'Email verification required', + 'verification_intro' => 'Please verify your email address by clicking on the link we emailed to you. If you didn’t receive the email, we will gladly send you another.', + 'verification_sent' => 'A new verification link has been sent to the email address you provided.', + 'resend_verification_email' => 'Resend verification email', + 'verification_succeeded' => 'Your email address has been verified.', + 'two_factor_auth_code_intro' => 'Please enter the code provided by your authenticator application to complete the sign in process.', + 'label_two_factor_auth_code' => 'Authentication code', + 'two_factor_auth_action_use_recovery_code' => 'Use a recovery code', + 'two_factor_auth_recovery_code_intro' => 'Please enter one of your saved recovery codes to complete the sign in process.', + 'label_two_factor_auth_recovery_code' => 'Recovery code', + 'two_factor_auth_action_use_code' => 'Use an authentication code', + 'invalid_two_factor_auth_code' => 'The provided two-factor authentication code was not valid.', +]; diff --git a/resources/lang/en/dashboard.php b/resources/lang/en/dashboard.php new file mode 100644 index 0000000..af1db65 --- /dev/null +++ b/resources/lang/en/dashboard.php @@ -0,0 +1,6 @@ + 'Dashboard', + 'welcome' => 'Welcome, :name!', +]; diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php new file mode 100644 index 0000000..6d75dd8 --- /dev/null +++ b/resources/lang/en/errors.php @@ -0,0 +1,31 @@ + 'Not authorized', + 'error_401_message' => 'You are not authorized to visit this page. You may need to log in.', + 'error_403_title' => 'Access forbidden', + 'error_403_message' => 'You do not have permission to visit this page.', + 'error_404_title' => 'Not found', + 'error_404_message' => 'The page you are trying to visit could not be found.', + 'error_419_title' => 'Page expired', + 'error_419_message' => 'The page has expired. Please try again.', + 'error_429_title' => 'Too many requests', + 'error_429_message' => 'You have sent too many requests to this page. Please wait a few moments and try again.', + 'error_500_title' => 'Server error', + 'error_500_message' => 'Sorry, it looks like something isn’t working properly on our end.', + 'error_503_title' => 'Service unavailable', + 'error_503_message' => 'Sorry, it looks like the website can’t respond to your request right now. Please wait a few moments and try again.', + 'return_home' => 'Return to home page', +]; diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php new file mode 100644 index 0000000..4c9b1c2 --- /dev/null +++ b/resources/lang/en/forms.php @@ -0,0 +1,8 @@ + 'Save changes', + 'label_email' => 'Email address', + 'errors_found' => 'Errors found', + 'errors_found_message' => 'Sorry, some errors were found in your submission. Please correct these errors and try again.', +]; diff --git a/resources/lang/en/mail.php b/resources/lang/en/mail.php new file mode 100644 index 0000000..f03ef32 --- /dev/null +++ b/resources/lang/en/mail.php @@ -0,0 +1,8 @@ + 'Sorry!', + 'greeting' => 'Hello!', + 'salutation' => 'Regards', + 'link_guidance' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:', +]; diff --git a/resources/lang/en/nav.php b/resources/lang/en/nav.php new file mode 100644 index 0000000..fb1d6df --- /dev/null +++ b/resources/lang/en/nav.php @@ -0,0 +1,5 @@ + 'Languages', +]; diff --git a/resources/lang/en/passwords.php b/resources/lang/en/passwords.php new file mode 100644 index 0000000..2345a56 --- /dev/null +++ b/resources/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset!', + 'sent' => 'We have emailed your password reset link!', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/resources/lang/en/routes.php b/resources/lang/en/routes.php new file mode 100644 index 0000000..8bb43d4 --- /dev/null +++ b/resources/lang/en/routes.php @@ -0,0 +1,8 @@ + 'dashboard', + 'logout' => 'logout', + 'login' => 'login', + 'register' => 'register', +]; diff --git a/resources/lang/en/user.php b/resources/lang/en/user.php new file mode 100644 index 0000000..4a96e01 --- /dev/null +++ b/resources/lang/en/user.php @@ -0,0 +1,24 @@ + 'Settings', + 'account' => 'Account', + 'label_name' => 'Full name', + 'label_locale' => 'Preferred language', + 'two_factor_auth' => 'Two-factor authentication', + 'two_factor_auth_intro' => 'Add additional security to your account using two-factor authentication.', + 'two_factor_auth_not_enabled' => 'You have not enabled two-factor authentication.', + 'two_factor_auth_enabled' => 'You have enabled two-factor authentication.', + 'two_factor_auth_disabled' => 'You have disabled two-factor authentication.', + 'two_factor_auth_qr_code' => 'Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.', + 'two_factor_auth_recovery_codes' => 'Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.', + 'two_factor_auth_recovery_codes_regenerated' => 'New recovery codes have been generated.', + 'action_enable_two_factor_auth' => 'Enable two-factor authentication', + 'action_disable_two_factor_auth' => 'Disable two-factor authentication', + 'action_regenerate_two_factor_auth_recovery_codes' => 'Regenerate recovery codes', + 'delete_account' => 'Delete account', + 'delete_account_intro' => 'Your account will be deleted and cannot be recovered. If you still want to delete your account, please enter your current password to proceed.', + 'action_delete_account' => 'Delete my account', + 'settings_update_succeeded' => 'Your settings have been saved.', + 'destroy_succeeded' => 'Your account has been deleted.', +]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php new file mode 100644 index 0000000..01eb3d1 --- /dev/null +++ b/resources/lang/en/validation.php @@ -0,0 +1,14 @@ + [ + 'length-uppercase' => 'The :attribute must be at least :length characters and contain at least one uppercase character.', + 'length-numeric' => 'The :attribute must be at least :length characters and contain at least one number.', + 'length-specialcharacter' => 'The :attribute must be at least :length characters and contain at least one special character.', + 'length-uppercase-numeric' => 'The :attribute must be at least :length characters and contain at least one uppercase character and one number.', + 'length-uppercase-specialcharacter' => 'The :attribute must be at least :length characters and contain at least one uppercase character and one special character.', + 'length-uppercase-numeric-specialcharacter' => 'The :attribute must be at least :length characters and contain at least one uppercase character, one number, and one special character.', + 'length-numeric-specialcharacter' => 'The :attribute must be at least :length characters and contain at least one special character and one number.', + 'length' => 'The :attribute must be at least :length characters.', + ], +]; diff --git a/resources/lang/en/welcome.php b/resources/lang/en/welcome.php new file mode 100644 index 0000000..75c0843 --- /dev/null +++ b/resources/lang/en/welcome.php @@ -0,0 +1,6 @@ + 'Welcome to Hearth!', + 'details' => 'Make yourself at home.', +]; diff --git a/resources/lang/fr/alert.php b/resources/lang/fr/alert.php new file mode 100644 index 0000000..b2d255a --- /dev/null +++ b/resources/lang/fr/alert.php @@ -0,0 +1,8 @@ + 'Erreur', + 'warning' => 'Avertissement', + 'success' => 'Succès', + 'notice' => 'Annonce', +]; diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php new file mode 100644 index 0000000..b30c0ec --- /dev/null +++ b/resources/lang/fr/auth.php @@ -0,0 +1,48 @@ + 'S’identifier', + 'sign_out' => 'Se déconnecter', + 'create_account' => 'Créer un compte', + 'label_password' => 'Mot de passe', + 'label_password_confirmation' => 'Confirmez le mot de passe', + 'existing_account_prompt' => 'Vous avez déjà un compte?', + 'create_your_account' => 'Créez votre compte', + 'label_current_password' => 'Mot de passe actuel', + 'change_password' => 'Changer le mot de passe', + 'action_cancel' => 'Annuler', + 'action_confirm' => 'Confirmer', + 'password_change_succeeded' => 'Votre mot de passe a été modifié.', + 'label_remember_me' => 'Souvenez-vous de moi', + 'error_intro' => 'Désolé, quelque chose s’est mal passé.', + 'wrong_password' => 'Le mot de passe fourni est incorrect.', + 'confirm_intro' => 'Pour votre sécurité, veuillez confirmer votre mot de passe avant de continuer.', + 'forget_prompt' => 'Mot de passe oublié?', + 'forgot_intro' => 'Mot de passe oublié? Entrez votre adresse e-mail et nous vous enverrons par e-mail un lien de réinitialisation du mot de passe qui vous permettra d’en choisir un nouveau.', + 'forgot_submit' => 'Envoyer le lien de réinitialisation', + 'reset_submit' => 'Réinitialiser le mot de passe', + 'verification_required' => 'Vérification de l\'e-mail requise', + 'verification_intro' => 'Veuillez vérifier votre adresse e-mail en cliquant sur le lien que nous vous avons envoyé. Si vous n\'avez pas reçu le courriel, nous vous enverrons volontiers un autre.', + 'verification_sent' => 'Un nouveau lien de vérification a été envoyé à l\'adresse e-mail que vous avez fournie.', + 'resend_verification_email' => 'Renvoyer l’e-mail de vérification', + 'verification_succeeded' => 'Votre adresse e-mail a été vérifiée.', + 'two_factor_auth_code_intro' => 'Veuillez entrer le code fourni par votre application d\'authentificateur pour compléter le processus de connexion.', + 'label_two_factor_auth_code' => 'Code d\'authentification', + 'two_factor_auth_action_use_recovery_code' => 'Utiliser un code de récupération', + 'two_factor_auth_recovery_code_intro' => 'Veuillez entrer un de vos codes de récupération enregistrés pour terminer le processus de connexion.', + 'label_two_factor_auth_recovery_code' => 'Code de récupération', + 'two_factor_auth_action_use_code' => 'Utiliser un code d\'authentification', + 'invalid_two_factor_auth_code' => 'Le code d\'authentification à deux facteurs fourni n\'est pas valide.', +]; diff --git a/resources/lang/fr/dashboard.php b/resources/lang/fr/dashboard.php new file mode 100644 index 0000000..1f68871 --- /dev/null +++ b/resources/lang/fr/dashboard.php @@ -0,0 +1,6 @@ + 'Tableau de bord', + 'welcome' => 'Bienvenue, :name!', +]; diff --git a/resources/lang/fr/errors.php b/resources/lang/fr/errors.php new file mode 100644 index 0000000..235f977 --- /dev/null +++ b/resources/lang/fr/errors.php @@ -0,0 +1,31 @@ + 'Non autorisé', + 'error_401_message' => 'Vous n\'êtes pas autorisé à visiter cette page. Vous devrez peut-être vous connecter.', + 'error_403_title' => 'Accès interdit', + 'error_403_message' => 'Vous n\'avez pas la permission de visiter cette page.', + 'error_404_title' => 'Non trouvé', + 'error_404_message' => 'La page que vous essayez de visiter est introuvable.', + 'error_419_title' => 'Page expirée', + 'error_419_message' => 'La page a expiré. Veuillez réessayer.', + 'error_429_title' => 'Trop de demandes', + 'error_429_message' => 'Vous avez envoyé trop de demandes à cette page. Veuillez patienter quelques instants et réessayer.', + 'error_500_title' => 'Erreur de serveur', + 'error_500_message' => 'Désolé, il semble que quelque chose ne fonctionne pas correctement de notre côté.', + 'error_503_title' => 'Service indisponible', + 'error_503_message' => 'Désolé, il semble que le site web ne puisse pas répondre à votre demande pour le moment. Veuillez patienter quelques instants et réessayer.', + 'return_home' => 'Retour à la page d\'accueil', +]; diff --git a/resources/lang/fr/forms.php b/resources/lang/fr/forms.php new file mode 100644 index 0000000..43c5629 --- /dev/null +++ b/resources/lang/fr/forms.php @@ -0,0 +1,8 @@ + 'Sauvegarder les modifications', + 'label_email' => 'Adresse e-mail', + 'errors_found' => 'Erreurs trouvées', + 'errors_found_message' => 'Désolé, certaines erreurs ont été trouvées dans votre envoi. Veuillez corriger ces erreurs et réessayer.', +]; diff --git a/resources/lang/fr/mail.php b/resources/lang/fr/mail.php new file mode 100644 index 0000000..3517588 --- /dev/null +++ b/resources/lang/fr/mail.php @@ -0,0 +1,8 @@ + 'Désolé!', + 'greeting' => 'Bonjour!', + 'salutation' => 'Régards', + 'link_guidance' => 'Si vous rencontrez des difficultés à cliquer sur le bouton ":actionText", copiez et collez l\'URL ci-dessous dans votre navigateur Web:', +]; diff --git a/resources/lang/fr/nav.php b/resources/lang/fr/nav.php new file mode 100644 index 0000000..76ba24b --- /dev/null +++ b/resources/lang/fr/nav.php @@ -0,0 +1,5 @@ + 'Langues', +]; diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php new file mode 100644 index 0000000..8bea7a9 --- /dev/null +++ b/resources/lang/fr/passwords.php @@ -0,0 +1,22 @@ + 'Votre mot de passe a été réinitialisé!', + 'sent' => 'Nous avons envoyé votre lien de réinitialisation de mot de passe!', + 'throttled' => 'Veuillez patienter avant de réessayer.', + 'token' => 'Ce jeton de réinitialisation de mot de passe est invalide.', + 'user' => 'Nous ne pouvons pas trouver un utilisateur avec cette adresse e-mail.', + +]; diff --git a/resources/lang/fr/routes.php b/resources/lang/fr/routes.php new file mode 100644 index 0000000..4756a57 --- /dev/null +++ b/resources/lang/fr/routes.php @@ -0,0 +1,8 @@ + 'tableau-de-bord', + 'logout' => 'se-deconnecter', + 'login' => 'se-connecter', + 'register' => 'inscription', +]; diff --git a/resources/lang/fr/user.php b/resources/lang/fr/user.php new file mode 100644 index 0000000..e87e7cd --- /dev/null +++ b/resources/lang/fr/user.php @@ -0,0 +1,24 @@ + 'Paramètres', + 'account' => 'Compte', + 'label_name' => 'Nom complet', + 'label_locale' => 'Langue préférée', + 'two_factor_auth' => 'Authentification à deux facteurs', + 'two_factor_auth_intro' => 'Ajoutez une sécurité supplémentaire à votre compte en utilisant l\'authentification à deux facteurs.', + 'two_factor_auth_not_enabled' => 'Vous n\'avez pas activé l\'authentification à deux facteurs.', + 'two_factor_auth_enabled' => 'Vous avez activé l\'authentification à deux facteurs.', + 'two_factor_auth_disabled' => 'Vous avez désactivé l\'authentification à deux facteurs.', + 'two_factor_auth_qr_code' => 'L\'authentification à deux facteurs est maintenant activée. Scannez le code QR suivant à l\'aide de l\'application d\'authentification de votre téléphone.', + 'two_factor_auth_recovery_codes' => 'Enregistrez ces codes de récupération dans un gestionnaire de mots de passe sécurisé. Ils peuvent être utilisés pour récupérer l\'accès à votre compte si votre périphérique d\'authentification à deux facteurs est perdu.', + 'two_factor_auth_recovery_codes_regenerated' => 'De nouveaux codes de récupération ont été générés.', + 'action_enable_two_factor_auth' => 'Activer l\'authentification à deux facteurs', + 'action_disable_two_factor_auth' => 'Désactiver l\'authentification à deux facteurs', + 'action_regenerate_two_factor_auth_recovery_codes' => 'Régénérer les codes de récupération', + 'delete_account' => 'Supprimer le compte', + 'delete_account_intro' => 'Votre compte sera supprimé et ne pourra pas être récupéré. Si vous souhaitez toujours supprimer votre compte, veuillez entrer votre mot de passe actuel pour continuer.', + 'action_delete_account' => 'Supprimer le compte', + 'settings_update_succeeded' => 'Vos paramètres ont été enregistrés.', + 'destroy_succeeded' => 'Votre compte a été supprimé.', +]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php new file mode 100644 index 0000000..b45fe63 --- /dev/null +++ b/resources/lang/fr/validation.php @@ -0,0 +1,14 @@ + [ + 'length-uppercase' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère majuscule.', + 'length-numeric' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un chiffre.', + 'length-specialcharacter' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère spécial.', + 'length-uppercase-numeric' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère majuscule et un chiffre.', + 'length-uppercase-specialcharacter' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère majuscule et un caractère spécial.', + 'length-uppercase-numeric-specialcharacter' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère majuscule, un chiffre et un caractère spécial.', + 'length-numeric-specialcharacter' => 'Le champ :attribute doit comporter au moins :length caractères et contenir au moins un caractère spécial et un chiffre.', + 'length' => 'Le texte :attribute doit contenir au moins :min caractères.', + ], +]; diff --git a/resources/lang/fr/welcome.php b/resources/lang/fr/welcome.php new file mode 100644 index 0000000..ccff0c7 --- /dev/null +++ b/resources/lang/fr/welcome.php @@ -0,0 +1,6 @@ + 'Bienvenue chez Hearth!', + 'details' => 'Faites-vous comme chez vous.', +]; diff --git a/resources/views/alert.blade.php b/resources/views/alert.blade.php new file mode 100644 index 0000000..94c669e --- /dev/null +++ b/resources/views/alert.blade.php @@ -0,0 +1,4 @@ + diff --git a/resources/views/checkbox.blade.php b/resources/views/checkbox.blade.php new file mode 100644 index 0000000..63f18c4 --- /dev/null +++ b/resources/views/checkbox.blade.php @@ -0,0 +1,13 @@ +merge([ + 'name' => $name, + 'id' => $id, + ]) !!} + value="1" + {{ $required ? 'required' : '' }} + {{ $autofocus ? 'autofocus' : '' }} + @disabled($disabled) + @checked($checked) + {!! $describedBy() ? 'aria-describedby="' . $describedBy() . '"' : '' !!} + {!! $invalid ? 'aria-invalid="true"' : '' !!} +> diff --git a/resources/views/checkboxes.blade.php b/resources/views/checkboxes.blade.php new file mode 100644 index 0000000..75e42cc --- /dev/null +++ b/resources/views/checkboxes.blade.php @@ -0,0 +1,11 @@ +@foreach($options as $option) +
+ @php($id = Str::slug("{$name}-{$option['value']}")) + @php($hint = isset($option['hint']) ? $id . '-hint' : '') + + {{ $option['label'] }} + @if(isset($option['hint']) && !empty($option['hint'])) + {{ $option['hint'] }} + @endif +
+@endforeach diff --git a/resources/views/date-input.blade.php b/resources/views/date-input.blade.php new file mode 100644 index 0000000..712aee6 --- /dev/null +++ b/resources/views/date-input.blade.php @@ -0,0 +1,30 @@ +
+ + {{ $label ?? __('forms.label_date') }} + + + @if($hint) + {{ $hint }} + @endif + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ {{ __('validation.date', ['attribute' => strtolower($label)]) }} +
+
diff --git a/resources/views/error.blade.php b/resources/views/error.blade.php new file mode 100644 index 0000000..5604123 --- /dev/null +++ b/resources/views/error.blade.php @@ -0,0 +1,9 @@ +@error($field, $bag) +

+ {{ $message }} +

+@elseif($slot) +

+ {{ $slot }} +

+@enderror diff --git a/resources/views/hint.blade.php b/resources/views/hint.blade.php new file mode 100644 index 0000000..f84d95e --- /dev/null +++ b/resources/views/hint.blade.php @@ -0,0 +1 @@ +

{!! Str::inlineMarkdown($slot) !!}

diff --git a/resources/views/input.blade.php b/resources/views/input.blade.php new file mode 100644 index 0000000..c21b681 --- /dev/null +++ b/resources/views/input.blade.php @@ -0,0 +1,11 @@ +merge([ + 'name' => $name, + 'id' => $id, + ]) !!} + {{ $required ? 'required' : '' }} + {{ $autofocus ? 'autofocus' : '' }} + @disabled($disabled) + {!! $describedBy() ? 'aria-describedby="' . $describedBy() . '"' : '' !!} + {!! $invalid ? 'aria-invalid="true"' : '' !!} +> diff --git a/resources/views/label.blade.php b/resources/views/label.blade.php new file mode 100644 index 0000000..fd05bff --- /dev/null +++ b/resources/views/label.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/language-switcher.blade.php b/resources/views/language-switcher.blade.php new file mode 100644 index 0000000..613c7a7 --- /dev/null +++ b/resources/views/language-switcher.blade.php @@ -0,0 +1,13 @@ +merge() }}> + + {{ __('hearth-components::nav.languages') }} + + + + @foreach ($locales as $key => $locale ) + + {{ $locale }} + + @endforeach + + diff --git a/resources/views/locale-select.blade.php b/resources/views/locale-select.blade.php new file mode 100644 index 0000000..564059e --- /dev/null +++ b/resources/views/locale-select.blade.php @@ -0,0 +1,18 @@ + diff --git a/resources/views/password-confirmation.blade.php b/resources/views/password-confirmation.blade.php new file mode 100644 index 0000000..37842a6 --- /dev/null +++ b/resources/views/password-confirmation.blade.php @@ -0,0 +1,19 @@ +
merge(['class' => 'flow']) }} x-data="confirmPassword('{{ route('password.confirmation') }}', '{{ localized_route('password.confirm') }}')"> + {{ $slot }} + +
diff --git a/resources/views/radio-button.blade.php b/resources/views/radio-button.blade.php new file mode 100644 index 0000000..d37eb0a --- /dev/null +++ b/resources/views/radio-button.blade.php @@ -0,0 +1,12 @@ +merge([ + 'name' => $name, + 'id' => $id, + 'value' => $value, + ]) !!} + {{ $autofocus ? 'autofocus' : '' }} + @disabled($disabled) + @checked($checked) + {!! $describedBy() ? 'aria-describedby="' . $describedBy() . '"' : '' !!} + {!! $invalid ? 'aria-invalid="true"' : '' !!} +> diff --git a/resources/views/radio-buttons.blade.php b/resources/views/radio-buttons.blade.php new file mode 100644 index 0000000..cf7b2fc --- /dev/null +++ b/resources/views/radio-buttons.blade.php @@ -0,0 +1,11 @@ +@foreach($options as $option) +
+ @php($id = Str::slug("{$name}-{$option['value']}")) + @php($hint = isset($option['hint']) ? $id . '-hint' : '') + + {{ $option['label'] }} + @if(isset($option['hint']) && !empty($option['hint'])) + {{ $option['hint'] }} + @endif +
+@endforeach diff --git a/resources/views/select.blade.php b/resources/views/select.blade.php new file mode 100644 index 0000000..5091b89 --- /dev/null +++ b/resources/views/select.blade.php @@ -0,0 +1,15 @@ + diff --git a/resources/views/textarea.blade.php b/resources/views/textarea.blade.php new file mode 100644 index 0000000..7c2e755 --- /dev/null +++ b/resources/views/textarea.blade.php @@ -0,0 +1,11 @@ + diff --git a/resources/views/translatable-input.blade.php b/resources/views/translatable-input.blade.php new file mode 100644 index 0000000..b5def01 --- /dev/null +++ b/resources/views/translatable-input.blade.php @@ -0,0 +1,21 @@ +
+ + + +
+@foreach($locales as $locale) + @if($locale !== locale()) +
+

+ +

+ +
+ + +
+
+ @endif +@endforeach diff --git a/resources/views/translatable-textarea.blade.php b/resources/views/translatable-textarea.blade.php new file mode 100644 index 0000000..6de12e5 --- /dev/null +++ b/resources/views/translatable-textarea.blade.php @@ -0,0 +1,21 @@ +
+ + + +
+@foreach($locales as $locale) + @if($locale !== locale()) +
+

+ +

+ +
+ + +
+
+ @endif +@endforeach diff --git a/src/Components/Alert.php b/src/Components/Alert.php new file mode 100644 index 0000000..bc4d125 --- /dev/null +++ b/src/Components/Alert.php @@ -0,0 +1,43 @@ +type = $type; + + $this->title = $title ? $title : __('hearth-components::alert.'.$type); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::alert'); + } +} diff --git a/src/Components/Checkbox.php b/src/Components/Checkbox.php new file mode 100644 index 0000000..2c8b0cb --- /dev/null +++ b/src/Components/Checkbox.php @@ -0,0 +1,109 @@ +name = $name; + $this->id = $id ?? $this->name; + $this->bag = $bag; + $this->checked = $checked; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->autofocus = $autofocus; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::checkbox'); + } +} diff --git a/src/Components/Checkboxes.php b/src/Components/Checkboxes.php new file mode 100644 index 0000000..0d8c359 --- /dev/null +++ b/src/Components/Checkboxes.php @@ -0,0 +1,73 @@ +name = $name; + $this->options = $options; + $this->checked = $checked; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::checkboxes'); + } +} diff --git a/src/Components/DateInput.php b/src/Components/DateInput.php new file mode 100644 index 0000000..8ddabe7 --- /dev/null +++ b/src/Components/DateInput.php @@ -0,0 +1,177 @@ +name = $name; + $this->label = $label; + $this->value = $value; + $this->id = $id ?? $this->name; + $this->bag = $bag; + $this->hinted = $hint !== null; + $this->hint = $hint; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->months = [ + [ + 'value' => '01', + 'label' => __('forms.month_january'), + ], + [ + 'value' => '02', + 'label' => __('forms.month_february'), + ], + [ + 'value' => '03', + 'label' => __('forms.month_march'), + ], + [ + 'value' => '04', + 'label' => __('forms.month_april'), + ], + [ + 'value' => '05', + 'label' => __('forms.month_may'), + ], + [ + 'value' => '06', + 'label' => __('forms.month_june'), + ], + [ + 'value' => '07', + 'label' => __('forms.month_july'), + ], + [ + 'value' => '08', + 'label' => __('forms.month_august'), + ], + [ + 'value' => '09', + 'label' => __('forms.month_september'), + ], + [ + 'value' => '10', + 'label' => __('forms.month_october'), + ], + [ + 'value' => '11', + 'label' => __('forms.month_november'), + ], + [ + 'value' => '12', + 'label' => __('forms.month_december'), + ], + ]; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\View\View + */ + public function render() + { + return view('hearth-components::date-input', ['months' => $this->months]); + } +} diff --git a/src/Components/Error.php b/src/Components/Error.php new file mode 100644 index 0000000..9282d9c --- /dev/null +++ b/src/Components/Error.php @@ -0,0 +1,52 @@ +for = $for; + $this->field = $field ?? $for; + $this->bag = $bag; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::error'); + } +} diff --git a/src/Components/Hint.php b/src/Components/Hint.php new file mode 100644 index 0000000..8958494 --- /dev/null +++ b/src/Components/Hint.php @@ -0,0 +1,35 @@ +for = $for; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::hint'); + } +} diff --git a/src/Components/Input.php b/src/Components/Input.php new file mode 100644 index 0000000..ddef691 --- /dev/null +++ b/src/Components/Input.php @@ -0,0 +1,102 @@ +name = $name; + $this->id = $id ?? $this->name; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->autofocus = $autofocus; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::input'); + } +} diff --git a/src/Components/Label.php b/src/Components/Label.php new file mode 100644 index 0000000..e59649a --- /dev/null +++ b/src/Components/Label.php @@ -0,0 +1,35 @@ +value = $value; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::label'); + } +} diff --git a/src/Components/LanguageSwitcher.php b/src/Components/LanguageSwitcher.php new file mode 100644 index 0000000..59112dd --- /dev/null +++ b/src/Components/LanguageSwitcher.php @@ -0,0 +1,51 @@ +locales = []; + + foreach ($locales as $locale) { + $this->locales[$locale] = get_locale_name($locale, $locale); + } + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::language-switcher'); + } +} diff --git a/src/Components/LocaleSelect.php b/src/Components/LocaleSelect.php new file mode 100644 index 0000000..41657a3 --- /dev/null +++ b/src/Components/LocaleSelect.php @@ -0,0 +1,131 @@ +name = $name; + $this->selected = $selected; + $this->id = $id ?? $this->name; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->autofocus = $autofocus; + + $locales = config('locales.supported', [ + 'en', + 'fr', + ]); + + $this->locales = []; + + foreach ($locales as $locale) { + $this->locales[$locale] = get_locale_name($locale, $locale); + } + + $this->selected = $selected; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::locale-select'); + } +} diff --git a/src/Components/PasswordConfirmation.php b/src/Components/PasswordConfirmation.php new file mode 100644 index 0000000..c67b8ac --- /dev/null +++ b/src/Components/PasswordConfirmation.php @@ -0,0 +1,52 @@ +message = $message ?? __('hearth-components::auth.confirm_intro'); + + $this->cancel = $cancel ?? __('hearth-components::auth.action_cancel'); + + $this->confirm = $confirm ?? __('hearth-components::auth.action_confirm'); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::password-confirmation'); + } +} diff --git a/src/Components/RadioButton.php b/src/Components/RadioButton.php new file mode 100644 index 0000000..b0e247a --- /dev/null +++ b/src/Components/RadioButton.php @@ -0,0 +1,110 @@ +name = $name; + $this->value = $value; + $this->id = $id ?? $this->name.'-'.Str::slug($value); + $this->bag = $bag; + $this->checked = $checked; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->disabled = $disabled; + $this->autofocus = $autofocus; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::radio-button'); + } +} diff --git a/src/Components/RadioButtons.php b/src/Components/RadioButtons.php new file mode 100644 index 0000000..561b13b --- /dev/null +++ b/src/Components/RadioButtons.php @@ -0,0 +1,79 @@ +name = $name; + $this->options = $options; + $this->checked = $checked; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::radio-buttons'); + } +} diff --git a/src/Components/Select.php b/src/Components/Select.php new file mode 100644 index 0000000..08df0e2 --- /dev/null +++ b/src/Components/Select.php @@ -0,0 +1,121 @@ +name = $name; + $this->options = $options; + $this->selected = $selected; + $this->id = $id ?? $this->name; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->autofocus = $autofocus; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\View\View + */ + public function render() + { + return view('hearth-components::select'); + } +} diff --git a/src/Components/Textarea.php b/src/Components/Textarea.php new file mode 100644 index 0000000..de93c32 --- /dev/null +++ b/src/Components/Textarea.php @@ -0,0 +1,112 @@ +name = $name; + $this->id = $id ?? $this->name; + $this->value = $value; + $this->bag = $bag; + $this->hinted = $hinted; + $this->invalid = $this->hasErrors($this->name, $this->bag); + $this->required = $required; + $this->disabled = $disabled; + $this->autofocus = $autofocus; + } + + /** + * Get the view / contents that represent the component. + * + * @return \Illuminate\View\View + */ + public function render() + { + return view('hearth-components::textarea'); + } +} diff --git a/src/Components/TranslatableInput.php b/src/Components/TranslatableInput.php new file mode 100644 index 0000000..d1bba05 --- /dev/null +++ b/src/Components/TranslatableInput.php @@ -0,0 +1,53 @@ +name = $name; + $this->label = $label; + $this->locales = $locales ?? config('locales.supported'); + $this->model = $model; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::translatable-input'); + } +} diff --git a/src/Components/TranslatableTextarea.php b/src/Components/TranslatableTextarea.php new file mode 100644 index 0000000..0896862 --- /dev/null +++ b/src/Components/TranslatableTextarea.php @@ -0,0 +1,53 @@ +name = $name; + $this->label = $label; + $this->locales = $locales ?? config('locales.supported'); + $this->model = $model; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View + { + return view('hearth-components::translatable-textarea'); + } +} diff --git a/src/Facades/HearthComponents.php b/src/Facades/HearthComponents.php new file mode 100644 index 0000000..2576055 --- /dev/null +++ b/src/Facades/HearthComponents.php @@ -0,0 +1,16 @@ +name('hearth-components') + ->hasAssets() + ->hasViews() + ->hasViewComponent('hearth', Alert::class) + ->hasViewComponent('hearth', Checkbox::class) + ->hasViewComponent('hearth', Checkboxes::class) + ->hasViewComponent('hearth', DateInput::class) + ->hasViewComponent('hearth', Error::class) + ->hasViewComponent('hearth', Hint::class) + ->hasViewComponent('hearth', Input::class) + ->hasViewComponent('hearth', Label::class) + ->hasViewComponent('hearth', LanguageSwitcher::class) + ->hasViewComponent('hearth', LocaleSelect::class) + ->hasViewComponent('hearth', PasswordConfirmation::class) + ->hasViewComponent('hearth', RadioButton::class) + ->hasViewComponent('hearth', RadioButtons::class) + ->hasViewComponent('hearth', Select::class) + ->hasViewComponent('hearth', Textarea::class) + ->hasViewComponent('hearth', TranslatableInput::class) + ->hasViewComponent('hearth', TranslatableTextarea::class) + ->hasTranslations() + ->hasInstallCommand(function (InstallCommand $command) { + $command + ->publishAssets(); + }); + } +} diff --git a/src/Traits/AriaDescribable.php b/src/Traits/AriaDescribable.php new file mode 100644 index 0000000..d10b0d0 --- /dev/null +++ b/src/Traits/AriaDescribable.php @@ -0,0 +1,24 @@ +hinted) { + $descriptors[] = is_string($this->hinted) ? $this->hinted : $this->name.'-hint'; + } + + if ($this->invalid) { + $descriptors[] = str_replace(['[', ']'], ['_', ''], $this->name).'-error'; + } + + return implode(' ', array_filter($descriptors)); + } +} diff --git a/src/Traits/GeneratesTranslatableFieldAttributes.php b/src/Traits/GeneratesTranslatableFieldAttributes.php new file mode 100644 index 0000000..548ff6f --- /dev/null +++ b/src/Traits/GeneratesTranslatableFieldAttributes.php @@ -0,0 +1,42 @@ + $label, 'locale' => get_locale_name($locale)]); + } +} diff --git a/src/Traits/HandlesValidation.php b/src/Traits/HandlesValidation.php new file mode 100644 index 0000000..3d503ef --- /dev/null +++ b/src/Traits/HandlesValidation.php @@ -0,0 +1,33 @@ +session()->get('errors', new ViewErrorBag); + }); + + $name = str_replace(['[', ']'], ['.', ''], $name); + + foreach ($errors->getBag($bag)->keys() as $key) { + if (Str::startsWith($key, $name)) { + return true; + } + } + + return false; + } +} diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..952943c --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,26 @@ +get($code, $locale); + + return $capitalize ? Str::ucfirst($language->getName()) : $language->getName(); + } catch (CommerceGuys\Intl\Exception\UnknownLanguageException $e) { + return null; + } + } +} diff --git a/stubs/lang/en/auth.php b/stubs/lang/en/auth.php new file mode 100644 index 0000000..6598e2c --- /dev/null +++ b/stubs/lang/en/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/stubs/lang/en/forms.php b/stubs/lang/en/forms.php new file mode 100644 index 0000000..ab156ed --- /dev/null +++ b/stubs/lang/en/forms.php @@ -0,0 +1,26 @@ + 'Save changes', + 'label_email' => 'Email address', + 'label_locality' => 'City or town', + 'label_region' => 'Province or territory', + 'label_date' => 'Date', + 'label_year' => 'Year', + 'label_month' => 'Month', + 'label_day' => 'Day', + 'month_january' => 'January', + 'month_february' => 'February', + 'month_march' => 'March', + 'month_april' => 'April', + 'month_may' => 'May', + 'month_june' => 'June', + 'month_july' => 'July', + 'month_august' => 'August', + 'month_september' => 'September', + 'month_october' => 'October', + 'month_november' => 'November', + 'month_december' => 'December', + 'errors_found' => 'Errors found', + 'errors_found_message' => 'Sorry, some errors were found in your submission. Please correct these errors and try again.', +]; diff --git a/stubs/lang/en/invitation.php b/stubs/lang/en/invitation.php new file mode 100644 index 0000000..4918aa3 --- /dev/null +++ b/stubs/lang/en/invitation.php @@ -0,0 +1,20 @@ + 'Invite new member', + 'invite_intro' => 'You can send an email invitation to someone who you would like to add to this team. If they do not have an account yet, they will be invited to create one first.', + 'action_send_invitation' => 'Send invitation', + 'cancel_member_invitation_link' => 'Cancel invitation', + 'cancel_member_invitation_link_with_email' => 'Cancel invitation for :email', + 'create_invitation_succeeded' => 'Your invitation has been sent.', + 'invited_user_already_belongs_to_a_team' => 'This user already belongs to a team.', + 'invited_user_already_belongs_to_this_team' => 'This user already belongs to this team.', + 'cancel_invitation_succeeded' => 'The invitation has been cancelled.', + 'email_not_valid' => 'You are logged in as :email, but this invitation is for a different email address.', + 'accept_invitation_succeeded' => 'You have joined the :invitationable team.', + 'invitations_title' => 'Member invitations', + 'invitation_email' => 'Email', + 'invitation_status' => 'Status', + 'invitation_role' => 'Role', + 'member_invited' => 'Invited', +]; diff --git a/stubs/lang/en/membership.php b/stubs/lang/en/membership.php new file mode 100644 index 0000000..804afbb --- /dev/null +++ b/stubs/lang/en/membership.php @@ -0,0 +1,8 @@ + ':user’s role has been updated.', + 'remove_member_succeeded' => ':user has been removed from :membershipable.', + 'edit_user_role_title' => 'Edit :user’s role', + 'edit_user_role_intro' => 'Modifying the role will change :user’s privileges for their :membershipable membership.', +]; diff --git a/stubs/lang/en/organization.php b/stubs/lang/en/organization.php new file mode 100644 index 0000000..daf9903 --- /dev/null +++ b/stubs/lang/en/organization.php @@ -0,0 +1,32 @@ + 'Organizations', + 'create_title' => 'Create an organization', + 'label_name' => 'Organization name', + 'action_create' => 'Create organization', + 'members_title' => 'Organization members', + 'browse_all' => 'Browse all organizations', + 'none_found' => 'No organizations found.', + 'member_name' => 'Name', + 'member_status' => 'Status', + 'member_role' => 'Role', + 'member_active' => 'Active', + 'action_add_member' => 'Add member', + 'delete_title' => 'Delete organization', + 'delete_intro' => 'Your organization will be deleted and cannot be recovered. If you still want to delete your organization, please enter your current password to proceed.', + 'action_delete' => 'Delete organization', + 'create_succeeded' => 'Your organization has been created.', + 'update_succeeded' => 'Your organization has been updated.', + 'destroy_succeeded' => 'Your organization has been deleted.', + 'edit_title' => 'Edit organization', + 'edit_organization' => 'Edit organization', + 'edit_user_role_link' => 'Edit', + 'edit_user_role_link_with_name' => 'Edit :user’s role', + 'label_user_role' => 'Role', + 'action_update_user_role' => 'Update Role', + 'action_cancel_user_role_update' => 'Cancel', + 'action_remove_member' => 'Remove', + 'action_remove_member_with_name' => 'Remove :user from :organization', + 'error_new_administrator_required_before_user_deletion' => 'You must assign a new administrator to your organization, :organization, before deleting your account.', +]; diff --git a/stubs/lang/en/pagination.php b/stubs/lang/en/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/stubs/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/stubs/lang/en/passwords.php b/stubs/lang/en/passwords.php new file mode 100644 index 0000000..2345a56 --- /dev/null +++ b/stubs/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset!', + 'sent' => 'We have emailed your password reset link!', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/stubs/lang/en/resource-collection.php b/stubs/lang/en/resource-collection.php new file mode 100644 index 0000000..23b778b --- /dev/null +++ b/stubs/lang/en/resource-collection.php @@ -0,0 +1,19 @@ + 'Create a resource collection', + 'label_title' => 'Resource collection title', + 'label_language' => 'Resource collection language', + 'label_description' => 'Resource collection description', + 'action_create' => 'Create resource collection', + 'edit_title' => 'Edit resource collection', + 'edit_resource_collection' => 'Edit resource collection', + 'action_delete' => 'Delete resource collection', + 'delete_title' => 'Delete resource collection', + 'delete_intro' => 'Your resource collection will be deleted and cannot be recovered. If you still want to delete your resource collection, please enter your current password to proceed.', + 'create_succeeded' => 'Your resource collection has been created.', + 'update_succeeded' => 'Your resource collection has been updated.', + 'destroy_succeeded' => 'Your resource collection has been deleted.', + 'index_title' => 'Resource collections', + 'none_found' => 'No resource collections found.', +]; diff --git a/stubs/lang/en/resource-select.php b/stubs/lang/en/resource-select.php new file mode 100644 index 0000000..1b1cd83 --- /dev/null +++ b/stubs/lang/en/resource-select.php @@ -0,0 +1,12 @@ + 'Action button', + 'add_resource' => 'Add resource', + 'available_resources' => 'Available resources', + 'remove_resource' => 'Remove resource', + 'resource_link' => 'Resource link (Opens in a new tab)', + 'resource_preview' => 'Resource preview', + 'resource_title' => 'Resource title', + 'selected_resources' => 'Selected resources', +]; diff --git a/stubs/lang/en/resource.php b/stubs/lang/en/resource.php new file mode 100644 index 0000000..4ea87c7 --- /dev/null +++ b/stubs/lang/en/resource.php @@ -0,0 +1,17 @@ + 'Create a resource', + 'label_title' => 'Resource title', + 'label_language' => 'Resource language', + 'label_summary' => 'Resource summary', + 'action_create' => 'Create resource', + 'edit_title' => 'Edit resource', + 'edit_resource' => 'Edit resource', + 'action_delete' => 'Delete resource', + 'delete_title' => 'Delete resource', + 'delete_intro' => 'Your resource will be deleted and cannot be recovered. If you still want to delete your resource, please enter your current password to proceed.', + 'create_succeeded' => 'Your resource has been created.', + 'update_succeeded' => 'Your resource has been updated.', + 'destroy_succeeded' => 'Your resource has been deleted.', +]; diff --git a/stubs/lang/en/roles.php b/stubs/lang/en/roles.php new file mode 100644 index 0000000..00962ea --- /dev/null +++ b/stubs/lang/en/roles.php @@ -0,0 +1,6 @@ + 'Administrator', + 'member' => 'Member', +]; diff --git a/stubs/lang/en/routes.php b/stubs/lang/en/routes.php new file mode 100644 index 0000000..02383d2 --- /dev/null +++ b/stubs/lang/en/routes.php @@ -0,0 +1,9 @@ + 'dashboard', + 'logout' => 'logout', + 'login' => 'login', + 'register' => 'register', + 'verification.verify' => '/verify-email/{id}/{hash}', +]; diff --git a/stubs/lang/en/validation.php b/stubs/lang/en/validation.php new file mode 100644 index 0000000..99fe484 --- /dev/null +++ b/stubs/lang/en/validation.php @@ -0,0 +1,162 @@ + 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute must only contain letters.', + 'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.', + 'alpha_num' => 'The :attribute must only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ + 'numeric' => 'The :attribute must be between :min and :max.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], + 'max' => [ + 'numeric' => 'The :attribute must not be greater than :max.', + 'file' => 'The :attribute must not be greater than :max kilobytes.', + 'string' => 'The :attribute must not be greater than :max characters.', + 'array' => 'The :attribute must not have more than :max items.', + ], + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'numeric' => 'The :attribute must be at least :min.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', + ], + 'multiple_of' => 'The :attribute must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'password' => 'The password is incorrect.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => 'The :attribute must be :size.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'membership' => [ + 'not_last_admin' => 'There must be at least one administrator.', + ], + 'organization' => [ + 'name_exists' => 'An organization with this name already exists.', + ], + 'resource' => [ + 'title_exists' => 'A resource with this title already exists.', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/stubs/lang/fr/auth.php b/stubs/lang/fr/auth.php new file mode 100644 index 0000000..c1a529c --- /dev/null +++ b/stubs/lang/fr/auth.php @@ -0,0 +1,20 @@ + 'Ces identifiants ne correspondent pas à nos enregistrements.', + 'password' => 'Le mot de passe fourni est incorrect.', + 'throttle' => 'Trop de tentatives de connexion. Veuillez réessayer dans :seconds secondes.', + +]; diff --git a/stubs/lang/fr/forms.php b/stubs/lang/fr/forms.php new file mode 100644 index 0000000..5f0a454 --- /dev/null +++ b/stubs/lang/fr/forms.php @@ -0,0 +1,26 @@ + 'Sauvegarder les modifications', + 'label_email' => 'Adresse e-mail', + 'label_locality' => 'Ville', + 'label_region' => 'Province ou territoire', + 'label_date' => 'Date', + 'label_year' => 'Année', + 'label_month' => 'Mois', + 'label_day' => 'Jour', + 'month_january' => 'Janvier', + 'month_february' => 'Février', + 'month_march' => 'Mars', + 'month_april' => 'Avril', + 'month_may' => 'Mai', + 'month_june' => 'Juin', + 'month_july' => 'Juillet', + 'month_august' => 'Août', + 'month_september' => 'Septembre', + 'month_october' => 'Octobre', + 'month_november' => 'Novembre', + 'month_december' => 'Décembre', + 'errors_found' => 'Erreurs trouvées', + 'errors_found_message' => 'Désolé, certaines erreurs ont été trouvées dans votre envoi. Veuillez corriger ces erreurs et réessayer.', +]; diff --git a/stubs/lang/fr/invitation.php b/stubs/lang/fr/invitation.php new file mode 100644 index 0000000..0a605fb --- /dev/null +++ b/stubs/lang/fr/invitation.php @@ -0,0 +1,20 @@ + 'Inviter un nouveau membre', + 'invite_intro' => 'Vous pouvez envoyer une invitation par courriel à quelqu\'un que vous souhaitez ajouter à cette équipe. S\'ils n\'ont pas encore de compte, ils seront invités à en créer un d\'abord.', + 'action_send_invitation' => 'Envoyer l\'invitation', + 'cancel_member_invitation_link' => 'Annuler l\'invitation', + 'cancel_member_invitation_link_with_email' => 'Annuler l\'invitation pour :email', + 'create_invitation_succeeded' => 'Votre invitation a été envoyée.', + 'invited_user_already_belongs_to_a_team' => 'This user already belongs to a team.', + 'invited_user_already_belongs_to_this_team' => 'This user already belongs to this team.', + 'cancel_invitation_succeeded' => 'L\'invitation a été annulée.', + 'email_not_valid' => 'Vous êtes connecté en tant que :email, mais cette invitation est pour une autre adresse e-mail.', + 'accept_invitation_succeeded' => 'You have joined the :invitationable team.', + 'invitations_title' => 'Invitations des membres', + 'invitation_email' => 'Courriel', + 'invitation_status' => 'Statut', + 'invitation_role' => 'Rôle', + 'member_invited' => 'Invité', +]; diff --git a/stubs/lang/fr/membership.php b/stubs/lang/fr/membership.php new file mode 100644 index 0000000..ca0bde1 --- /dev/null +++ b/stubs/lang/fr/membership.php @@ -0,0 +1,8 @@ + 'Le rôle de :user a été mis à jour.', + 'remove_member_succeeded' => ':user has been removed from :membershipable.', + 'edit_user_role_title' => 'Modifier le rôle de :user', + 'edit_user_role_intro' => 'Modifying the role will change :user’s privileges for their :membershipable membership.', +]; diff --git a/stubs/lang/fr/organization.php b/stubs/lang/fr/organization.php new file mode 100644 index 0000000..6445f0e --- /dev/null +++ b/stubs/lang/fr/organization.php @@ -0,0 +1,32 @@ + 'Organisations', + 'create_title' => 'Créer une organisation', + 'label_name' => 'Nom de l\'organisation', + 'action_create' => 'Créer une organisation', + 'members_title' => 'Membres de l\'organisation', + 'browse_all' => 'Parcourir toutes les organisations', + 'none_found' => 'Aucune organisation trouvée.', + 'member_name' => 'Nom', + 'member_status' => 'Statut', + 'member_role' => 'Rôle', + 'member_active' => 'Actif', + 'action_add_member' => 'Ajouter un membre', + 'delete_title' => 'Supprimer l\'organisation', + 'delete_intro' => 'Votre organisation sera supprimée et ne pourra pas être récupérée. Si vous souhaitez toujours supprimer votre organisation, veuillez entrer votre mot de passe actuel pour continuer.', + 'action_delete' => 'Supprimer l\'organisation', + 'create_succeeded' => 'Votre organisation a été créée.', + 'update_succeeded' => 'Votre organisation a été mise à jour.', + 'destroy_succeeded' => 'Votre organisation a été supprimée.', + 'edit_title' => 'Modifier l\'organisation', + 'edit_organization' => 'Modifier l\'organisation', + 'edit_user_role_link' => 'Modifier', + 'edit_user_role_link_with_name' => 'Modifier le rôle de :user', + 'label_user_role' => 'Rôle', + 'action_update_user_role' => 'Mettre à jour le rôle', + 'action_cancel_user_role_update' => 'Annuler', + 'action_remove_member' => 'Enlever', + 'action_remove_member_with_name' => 'Enlever :user de :organization', + 'error_new_administrator_required_before_user_deletion' => 'You must assign a new administrator to your organization, :organization, before deleting your account.', +]; diff --git a/stubs/lang/fr/pagination.php b/stubs/lang/fr/pagination.php new file mode 100644 index 0000000..8eff374 --- /dev/null +++ b/stubs/lang/fr/pagination.php @@ -0,0 +1,19 @@ + '« Précédent', + 'next' => 'Suivant »', + +]; diff --git a/stubs/lang/fr/passwords.php b/stubs/lang/fr/passwords.php new file mode 100644 index 0000000..d553ff3 --- /dev/null +++ b/stubs/lang/fr/passwords.php @@ -0,0 +1,22 @@ + 'Votre mot de passe a été réinitialisé!', + 'sent' => 'Nous vous avons envoyé par email le lien de réinitialisation du mot de passe!', + 'throttled' => 'Veuillez patienter avant de réessayer.', + 'token' => 'Ce jeton de réinitialisation du mot de passe n\'est pas valide.', + 'user' => "Aucun utilisateur n'a été trouvé avec cette adresse email.", + +]; diff --git a/stubs/lang/fr/resource-collection.php b/stubs/lang/fr/resource-collection.php new file mode 100644 index 0000000..b315aa7 --- /dev/null +++ b/stubs/lang/fr/resource-collection.php @@ -0,0 +1,19 @@ + 'Créer une collection de ressources', + 'label_title' => 'Titre de la collection de ressources', + 'label_language' => 'Langue de la collection de ressources', + 'label_description' => 'Description de la collection de ressources', + 'action_create' => 'Créer la collection de ressources', + 'edit_title' => 'Modifier la collection de ressources', + 'edit_resource_collection' => 'Éditer la collection de ressources', + 'action_delete' => 'Supprimer la collection de ressources', + 'delete_title' => 'Supprimer la collection de ressources', + 'delete_intro' => 'Votre collection de ressources sera supprimée et ne pourra pas être récupérée. Si vous voulez toujours supprimer votre collection, veuillez entrer votre mot de passe actuel pour continuer.', + 'create_succeeded' => 'Votre collection de ressources a été créée.', + 'update_succeeded' => 'Votre collection de ressources a été mise à jour.', + 'destroy_succeeded' => 'Votre collection de ressources a été supprimée.', + 'index_title' => 'Resource collections', + 'none_found' => 'No resource collections found.', +]; diff --git a/stubs/lang/fr/resource-select.php b/stubs/lang/fr/resource-select.php new file mode 100644 index 0000000..1b1cd83 --- /dev/null +++ b/stubs/lang/fr/resource-select.php @@ -0,0 +1,12 @@ + 'Action button', + 'add_resource' => 'Add resource', + 'available_resources' => 'Available resources', + 'remove_resource' => 'Remove resource', + 'resource_link' => 'Resource link (Opens in a new tab)', + 'resource_preview' => 'Resource preview', + 'resource_title' => 'Resource title', + 'selected_resources' => 'Selected resources', +]; diff --git a/stubs/lang/fr/resource.php b/stubs/lang/fr/resource.php new file mode 100644 index 0000000..3645cd7 --- /dev/null +++ b/stubs/lang/fr/resource.php @@ -0,0 +1,17 @@ + 'Créer une ressource', + 'label_title' => 'Titre de ressource', + 'label_language' => 'Langue de ressource', + 'label_summary' => 'Résumé de la ressource', + 'action_create' => 'Créer la ressource', + 'edit_title' => 'Modifier la ressource', + 'edit_resource' => 'Modifier la ressource', + 'action_delete' => 'Supprimer la ressource', + 'delete_title' => 'Supprimer la ressource', + 'delete_intro' => 'Votre ressource sera supprimée et ne pourra pas être récupérée. Si vous voulez toujours supprimer votre ressource, veuillez entrer votre mot de passe actuel pour continuer.', + 'create_succeeded' => 'Votre ressource a été créée.', + 'update_succeeded' => 'Votre ressource a été mise à jour.', + 'destroy_succeeded' => 'Votre ressource a été supprimée.', +]; diff --git a/stubs/lang/fr/roles.php b/stubs/lang/fr/roles.php new file mode 100644 index 0000000..9351d1a --- /dev/null +++ b/stubs/lang/fr/roles.php @@ -0,0 +1,6 @@ + 'Administrateur', + 'member' => 'Membre', +]; diff --git a/stubs/lang/fr/routes.php b/stubs/lang/fr/routes.php new file mode 100644 index 0000000..508ea5c --- /dev/null +++ b/stubs/lang/fr/routes.php @@ -0,0 +1,9 @@ + 'tableau-de-bord', + 'logout' => 'se-deconnecter', + 'login' => 'se-connecter', + 'register' => 'inscription', + 'verification.verify' => '/verification-de-courriel/{id}/{hash}', +]; diff --git a/stubs/lang/fr/validation.php b/stubs/lang/fr/validation.php new file mode 100644 index 0000000..422a39b --- /dev/null +++ b/stubs/lang/fr/validation.php @@ -0,0 +1,162 @@ + 'Le champ :attribute doit être accepté.', + 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', + 'after' => 'Le champ :attribute doit être une date postérieure au :date.', + 'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.', + 'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.', + 'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres et des tirets.', + 'alpha_num' => 'Le champ :attribute doit contenir uniquement des chiffres et des lettres.', + 'array' => 'Le champ :attribute doit être un tableau.', + 'before' => 'Le champ :attribute doit être une date antérieure au :date.', + 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', + 'between' => [ + 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', + 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', + 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', + 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', + ], + 'boolean' => 'Le champ :attribute doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', + 'current_password' => 'Le mot de passe est incorrect.', + 'date' => 'Le champ :attribute n\'est pas une date valide.', + 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', + 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', + 'different' => 'Les champs :attribute et :other doivent être différents.', + 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', + 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', + 'dimensions' => 'La taille de l\'image :attribute n\'est pas conforme.', + 'distinct' => 'Le champ :attribute a une valeur en double.', + 'email' => 'Le champ :attribute doit être une adresse email valide.', + 'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values.', + 'exists' => 'Le champ :attribute sélectionné est invalide.', + 'file' => 'Le champ :attribute doit être un fichier.', + 'filled' => 'Le champ :attribute doit avoir une valeur.', + 'gt' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', + ], + 'gte' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', + ], + 'image' => 'Le champ :attribute doit être une image.', + 'in' => 'Le champ :attribute est invalide.', + 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', + 'integer' => 'Le champ :attribute doit être un entier.', + 'ip' => 'Le champ :attribute doit être une adresse IP valide.', + 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', + 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', + 'json' => 'Le champ :attribute doit être un document JSON valide.', + 'lt' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', + ], + 'lte' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au plus :value éléments.', + ], + 'max' => [ + 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', + 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', + 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', + 'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.', + ], + 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'min' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :min éléments.', + ], + 'multiple_of' => 'La valeur de :attribute doit être un multiple de :value.', + 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', + 'not_regex' => 'Le format du champ :attribute n\'est pas valide.', + 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'password' => 'Le mot de passe est incorrect.', + 'present' => 'Le champ :attribute doit être présent.', + 'regex' => 'Le format du champ :attribute est invalide.', + 'required' => 'Le champ :attribute est obligatoire.', + 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', + 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', + 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', + 'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.', + 'required_without' => 'Le champ :attribute est obligatoire quand :values n\'est pas présent.', + 'required_without_all' => 'Le champ :attribute est requis quand aucun de :values n\'est présent.', + 'prohibited' => 'Le champ :attribute est interdit.', + 'prohibited_if' => 'Le champ :attribute est interdit quand :other a la valeur :value.', + 'prohibited_unless' => 'Le champ :attribute est interdit à moins que :other est l\'une des valeurs :values.', + 'same' => 'Les champs :attribute et :other doivent être identiques.', + 'size' => [ + 'numeric' => 'La valeur de :attribute doit être :size.', + 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', + 'string' => 'Le texte de :attribute doit contenir :size caractères.', + 'array' => 'Le tableau :attribute doit contenir :size éléments.', + ], + 'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values.', + 'string' => 'Le champ :attribute doit être une chaîne de caractères.', + 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', + 'unique' => 'La valeur du champ :attribute est déjà utilisée.', + 'uploaded' => 'Le fichier du champ :attribute n\'a pu être téléversé.', + 'url' => 'Le format du champ :attribute n\'est pas valide.', + 'uuid' => 'Le champ :attribute doit être un UUID valide.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'membership' => [ + 'not_last_admin' => 'Il faut avoir au moins un administrateur.', + ], + 'organization' => [ + 'name_exists' => 'Une organisation avec ce nom existe déjà.', + ], + 'resource' => [ + 'title_exists' => 'Une ressource avec ce titre existe déjà.', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/tests/ArchTest.php b/tests/ArchTest.php new file mode 100644 index 0000000..87fb64c --- /dev/null +++ b/tests/ArchTest.php @@ -0,0 +1,5 @@ +expect(['dd', 'dump', 'ray']) + ->each->not->toBeUsed(); diff --git a/tests/Components/CheckboxTest.php b/tests/Components/CheckboxTest.php new file mode 100644 index 0000000..31ad1de --- /dev/null +++ b/tests/Components/CheckboxTest.php @@ -0,0 +1,65 @@ +withViewErrors([]) + ->component( + Checkbox::class, + [ + 'name' => 'remember', + ], + ); + + $view->assertSee('id="remember"', false); +}); + +test('checkbox component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + Checkbox::class, + [ + 'name' => 'remember', + 'hinted' => true, + ], + ); + + $view->assertSee('aria-describedby="remember-hint"', false); +}); + +test('checkbox component references custom hint', function () { + $view = $this->withViewErrors([]) + ->component( + Checkbox::class, + [ + 'name' => 'remember', + 'hinted' => 'remember-login-hint', + ], + ); + + $view->assertSee('aria-describedby="remember-login-hint"', false); +}); + +test('checkbox component includes attribute', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'remember', + ], + ); + + $view->assertSee('x-model="remember"', false); +}); + +test('checkbox component can be checked', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'remember', + ], + ); + + $view->assertSee('checked', false); +}); diff --git a/tests/Components/CheckboxesTest.php b/tests/Components/CheckboxesTest.php new file mode 100644 index 0000000..943586c --- /dev/null +++ b/tests/Components/CheckboxesTest.php @@ -0,0 +1,208 @@ +withViewErrors([]) + ->component( + Checkboxes::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="flavour-chocolate"', false); + $view->assertSee('id="flavour-vanilla"', false); +}); + +test('checkboxes component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + Checkboxes::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + 'hinted' => true, + ], + ); + + $view->assertSee('aria-describedby="flavour-hint"', false); +}); + +test('checkboxes component references custom hint', function () { + $view = $this->withViewErrors([]) + ->component( + Checkboxes::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + 'hinted' => 'favourite-flavour-hint', + ], + ); + + $view->assertSee('aria-describedby="favourite-flavour-hint"', false); +}); + +test('checkboxes reference individual hints', function () { + $view = $this->withViewErrors([]) + ->component( + Checkboxes::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + 'hint' => 'Rich and delicate.', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + 'hint' => 'Decadent and delicious.', + ], + ], + 'hinted' => 'favourite-flavour-hint', + ], + ); + + $view->assertSee('aria-describedby="flavour-chocolate-hint favourite-flavour-hint"', false); + $view->assertSee('aria-describedby="flavour-vanilla-hint favourite-flavour-hint"', false); +}); + +test('checkboxes component includes attribute', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('x-model="flavours"', false); +}); + +test('checkboxes component includes checked item', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + 'checked' => ['vanilla'], + ], + ); + + $view->assertSee('value="vanilla" checked', false); + $view->assertDontSee('value="chocolate" checked', false); +}); + +test('checkboxes component has valid ids', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'French vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="flavour-french-vanilla"', false); + + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'courses[dessert][ice-cream][flavour]', + 'options' => [ + [ + 'value' => 'French vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="coursesdessertice-creamflavour-french-vanilla"', false); +}); + +test('checkboxes component handles validation', function () { + $view = $this->withViewErrors(['flavour.0.required' => 'Vanilla is a required flavour.']) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'French vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('aria-invalid', false); +}); diff --git a/tests/Components/DateInputTest.php b/tests/Components/DateInputTest.php new file mode 100644 index 0000000..524fe7f --- /dev/null +++ b/tests/Components/DateInputTest.php @@ -0,0 +1,47 @@ +withViewErrors([]) + ->component( + DateInput::class, + ['name' => 'birthday', 'label' => 'Your birthday'] + ); + + $view->assertSee('name="birthday_year"', false); + $view->assertSee('id="birthday_year"', false); + $view->assertSee('name="birthday_month"', false); + $view->assertSee('id="birthday_month"', false); + $view->assertSee('name="birthday_day"', false); + $view->assertSee('id="birthday_day"', false); +}); + +test('date input component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + DateInput::class, + [ + 'name' => 'birthday', + 'label' => 'Your birthday', + 'hint' => 'If you add your birthday to your account, we\'ll send you a treat!', + ] + ); + + $view->assertSee('aria-describedby="birthday-hint"', false); +}); + +test('date input component handles validation error', function () { + $view = $this->withViewErrors(['birthday' => 'You entered a date that does not exist!']) + ->component( + DateInput::class, + [ + 'name' => 'birthday', + 'label' => 'Your birthday', + 'hint' => 'If you add your birthday to your account, we\'ll send you a treat!', + ] + ); + + $view->assertSee('aria-describedby="birthday-hint birthday-error"', false); + $view->assertSee('aria-invalid="true"', false); +}); diff --git a/tests/Components/HintTest.php b/tests/Components/HintTest.php new file mode 100644 index 0000000..1b364e9 --- /dev/null +++ b/tests/Components/HintTest.php @@ -0,0 +1,14 @@ +blade('Enter your given name.'); + + $view->assertSee('id="fname-hint"', false); + $view->assertSee("

Enter your given name.\n

", false); +}); + +test('hint component renders markdown', function () { + $view = $this->blade('Enter a valid [DOI](https://www.doi.org).'); + + $view->assertSee("

Enter a valid DOI.\n

", false); +}); diff --git a/tests/Components/InputTest.php b/tests/Components/InputTest.php new file mode 100644 index 0000000..f6206d9 --- /dev/null +++ b/tests/Components/InputTest.php @@ -0,0 +1,56 @@ +withViewErrors([]) + ->component( + Input::class, + ['name' => 'fname'] + ); + + $view->assertSee('name="fname"', false); + $view->assertSee('id="fname"', false); +}); + +test('input component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + Input::class, + ['name' => 'fname', 'hinted' => true] + ); + + $view->assertSee('aria-describedby="fname-hint"', false); +}); + +test('input component references supplied hint id', function () { + $view = $this->withViewErrors([]) + ->component( + Input::class, + ['name' => 'fname', 'hinted' => 'my-hint'] + ); + + $view->assertSee('aria-describedby="my-hint"', false); +}); + +test('input component handles validation error', function () { + $view = $this->withViewErrors(['fname' => 'You must enter your full name.']) + ->component( + Input::class, + ['name' => 'fname'] + ); + + $view->assertSee('aria-describedby="fname-error"', false); + $view->assertSee('aria-invalid="true"', false); +}); + +test('input component with array name handles validation error', function () { + $view = $this->withViewErrors(['links.0.url' => 'You must enter a valid URL.']) + ->component( + Input::class, + ['name' => 'links[0][url]'] + ); + + $view->assertSee('aria-describedby="links_0_url-error"', false); + $view->assertSee('aria-invalid="true"', false); +}); diff --git a/tests/Components/RadioButtonTest.php b/tests/Components/RadioButtonTest.php new file mode 100644 index 0000000..3125094 --- /dev/null +++ b/tests/Components/RadioButtonTest.php @@ -0,0 +1,70 @@ +withViewErrors([]) + ->component( + RadioButton::class, + [ + 'name' => 'flavour', + 'value' => 'vanilla', + ], + ); + + $view->assertSee('id="flavour-vanilla"', false); +}); + +test('radio button component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + RadioButton::class, + [ + 'name' => 'flavour', + 'value' => 'vanilla', + 'hinted' => true, + ], + ); + + $view->assertSee('aria-describedby="flavour-hint"', false); +}); + +test('radio button component references custom hint', function () { + $view = $this->withViewErrors([]) + ->component( + RadioButton::class, + [ + 'name' => 'flavour', + 'value' => 'vanilla', + 'hinted' => 'flavour-vanilla-hint', + ], + ); + + $view->assertSee('aria-describedby="flavour-vanilla-hint"', false); +}); + +test('radio button component includes attribute', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'value' => 'vanilla', + ], + ); + + $view->assertSee('x-model="flavour"', false); +}); + +test('radio button component can be checked', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'value' => 'vanilla', + ], + ); + + $view->assertSee('checked', false); +}); diff --git a/tests/Components/RadioButtonsTest.php b/tests/Components/RadioButtonsTest.php new file mode 100644 index 0000000..f3038a7 --- /dev/null +++ b/tests/Components/RadioButtonsTest.php @@ -0,0 +1,209 @@ +withViewErrors([]) + ->component( + RadioButtons::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="flavour-chocolate"', false); + $view->assertSee('id="flavour-vanilla"', false); +}); + +test('radio buttons component references hint', function () { + $view = $this->withViewErrors([]) + ->component( + RadioButtons::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + 'hinted' => true, + ], + ); + + $view->assertSee('aria-describedby="flavour-hint"', false); +}); + +test('radio buttons component references custom hint', function () { + $view = $this->withViewErrors([]) + ->component( + RadioButtons::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + 'hinted' => 'favourite-flavour-hint', + ], + ); + + $view->assertSee('aria-describedby="favourite-flavour-hint"', false); +}); + +test('radio buttons reference individual hints', function () { + $view = $this->withViewErrors([]) + ->component( + RadioButtons::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + 'hint' => 'Rich and delicate.', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + 'hint' => 'Decadent and delicious.', + ], + ], + 'hinted' => 'favourite-flavour-hint', + ], + ); + + $view->assertSee('aria-describedby="flavour-chocolate-hint favourite-flavour-hint"', false); + $view->assertSee('aria-describedby="flavour-vanilla-hint favourite-flavour-hint"', false); +}); + +test('radio buttons component includes attribute', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('x-model="flavour"', false); +}); + +test('radio buttons component includes checked button', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('value="vanilla" checked', false); + $view->assertDontSee('value="chocolate" checked', false); +}); + +test('radio buttons component includes checked boolean button', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'email_me', + 'options' => [ + [ + 'value' => '1', + 'label' => 'Yes', + ], + [ + 'value' => '0', + 'label' => 'No', + ], + ], + 'checked' => 1, + ], + ); + + $view->assertSee('value="1" checked', false); + $view->assertDontSee('value="0" checked', false); +}); + +test('radio buttons component component has valid ids', function () { + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'French vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="flavour-french-vanilla"', false); + + $view = $this->withViewErrors([]) + ->blade( + '', + [ + 'name' => 'courses[dessert][ice-cream][flavour]', + 'options' => [ + [ + 'value' => 'French vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('id="coursesdessertice-creamflavour-french-vanilla"', false); +}); diff --git a/tests/Components/SelectTest.php b/tests/Components/SelectTest.php new file mode 100644 index 0000000..ad4bc4a --- /dev/null +++ b/tests/Components/SelectTest.php @@ -0,0 +1,49 @@ +withViewErrors([]) + ->component( + Select::class, + [ + 'name' => 'flavour', + 'options' => [ + [ + 'value' => 'vanilla', + 'label' => 'Vanilla', + ], + [ + 'value' => 'chocolate', + 'label' => 'Chocolate', + ], + ], + ], + ); + + $view->assertSee('