mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Merge branch 'main' into v5/main
This commit is contained in:
commit
010308b0cd
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@ -20,7 +20,7 @@ https://github.com/strapi/strapi/blob/main/CONTRIBUTING.md#reporting-an-issue
|
||||
|
||||
### Required System information
|
||||
|
||||
<!-- Please ensure you are using the Node LTS version (v16 or v18 or v20) -->
|
||||
<!-- Please ensure you are using the Node LTS version (v18 or v20) -->
|
||||
<!-- Strapi v3 is no longer supported, please update to Strapi v4 -->
|
||||
<!-- If you are reporting a frontend bug please provide error logs after setting STRAPI_ENFORCE_SOURCEMAPS=true in your .env -->
|
||||
<!-- This environment variable makes frontend errors easier to read and trace -->
|
||||
|
||||
2
.github/actions/check-pr-status/README.md
vendored
2
.github/actions/check-pr-status/README.md
vendored
@ -19,7 +19,7 @@ This action checks a PR labels, milestone and status to validate it is ready for
|
||||
|
||||
### Requirements
|
||||
|
||||
- The code is compatible with Node 16, 18, and 20
|
||||
- The code is compatible with Node 18, and 20
|
||||
|
||||
### Dependencies
|
||||
|
||||
|
||||
2
.github/actions/check-pr-status/action.yml
vendored
2
.github/actions/check-pr-status/action.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: 'PR Checker'
|
||||
description: 'Check PR status for mergeability'
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
|
||||
3
.github/actions/run-api-tests/script.sh
vendored
3
.github/actions/run-api-tests/script.sh
vendored
@ -11,6 +11,5 @@ export JWT_SECRET="aSecret"
|
||||
opts=($DB_OPTIONS)
|
||||
jestOptions=($JEST_OPTIONS)
|
||||
|
||||
yarn nx run-many --target=build --nx-ignore-cycles --skip-nx-cache
|
||||
yarn run test:generate-app --appPath=test-apps/api "${opts[@]}"
|
||||
yarn run test:generate-app:no-build --appPath=test-apps/api "${opts[@]}"
|
||||
yarn run test:api --no-generate-app "${jestOptions[@]}"
|
||||
|
||||
19
.github/actions/run-build/action.yml
vendored
Normal file
19
.github/actions/run-build/action.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: 'Monorepo build (yarn)'
|
||||
description: 'Run yarn build with cache enabled'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
|
||||
steps:
|
||||
|
||||
- name: ♻️ Restore build cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-build-cache
|
||||
with:
|
||||
path: packages/**/dist
|
||||
key: yarn-build-cache-${{ github.sha }}
|
||||
- if: ${{ steps.yarn-build-cache.outputs.cache-hit != 'true' }}
|
||||
name: 📥 Run build
|
||||
shell: bash
|
||||
run: yarn nx run-many --target=build --nx-ignore-cycles --skip-nx-cache
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
- run: yarn
|
||||
- run: ./scripts/pre-publish.sh --yes
|
||||
env:
|
||||
|
||||
2
.github/workflows/publish-prerelease.yml
vendored
2
.github/workflows/publish-prerelease.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
- run: yarn
|
||||
- run: ./scripts/pre-publish.sh --yes
|
||||
env:
|
||||
|
||||
2
.github/workflows/remove-dist-tag.yml
vendored
2
.github/workflows/remove-dist-tag.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
- run: yarn
|
||||
- run: ./scripts/remove-dist-tag.sh
|
||||
env:
|
||||
|
||||
10
.github/workflows/skipped_tests.yml
vendored
10
.github/workflows/skipped_tests.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- run: echo "Skipped"
|
||||
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- run: echo "Skipped"
|
||||
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
name: '[CE] API Integration (postgres, node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- run: echo "Skipped"
|
||||
|
||||
@ -82,7 +82,7 @@ jobs:
|
||||
name: '[CE] API Integration (mysql, node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- run: echo "Skipped"
|
||||
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
name: '[CE] API Integration (mysql:5 , node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- run: echo "Skipped"
|
||||
|
||||
|
||||
124
.github/workflows/tests.yml
vendored
124
.github/workflows/tests.yml
vendored
@ -35,30 +35,39 @@ jobs:
|
||||
filters: .github/filters.yaml
|
||||
|
||||
lint:
|
||||
name: 'lint (node: ${{ matrix.node }})'
|
||||
needs: [changes]
|
||||
name: 'lint (node: 20)'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [18]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
node-version: 20
|
||||
- uses: nrwl/nx-set-shas@v3
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Run build
|
||||
run: yarn nx run-many --target=build --nx-ignore-cycles --skip-nx-cache
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- name: Run lint
|
||||
run: yarn nx affected --target=lint --parallel --nx-ignore-cycles
|
||||
|
||||
build:
|
||||
name: 'build (node: 20)'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
|
||||
typescript:
|
||||
name: 'typescript'
|
||||
needs: [changes]
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -70,8 +79,8 @@ jobs:
|
||||
- uses: nrwl/nx-set-shas@v3
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Run build
|
||||
run: yarn nx run-many --target=build --nx-ignore-cycles --skip-nx-cache
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- name: TSC for packages
|
||||
run: yarn nx affected --target=test:ts --nx-ignore-cycles
|
||||
- name: TSC for back
|
||||
@ -81,11 +90,11 @@ jobs:
|
||||
|
||||
unit_back:
|
||||
name: 'unit_back (node: ${{ matrix.node }})'
|
||||
needs: [changes, lint, typescript]
|
||||
needs: [changes, build]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@ -96,14 +105,14 @@ jobs:
|
||||
- uses: nrwl/nx-set-shas@v3
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Run build
|
||||
run: yarn build --skip-nx-cache
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- name: Run tests
|
||||
run: yarn nx affected --target=test:unit --nx-ignore-cycles
|
||||
|
||||
unit_front:
|
||||
name: 'unit_front (node: ${{ matrix.node }})'
|
||||
needs: [changes, lint, typescript]
|
||||
needs: [changes, build]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@ -118,31 +127,14 @@ jobs:
|
||||
- uses: nrwl/nx-set-shas@v3
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Run build:ts for admin-test-utils & helper-plugin
|
||||
run: yarn build --projects=@strapi/admin-test-utils,@strapi/helper-plugin --skip-nx-cache
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- name: Run test
|
||||
run: yarn nx affected --target=test:front --nx-ignore-cycles
|
||||
|
||||
build:
|
||||
name: 'build (node: ${{ matrix.node }})'
|
||||
needs: [changes, lint, typescript, unit_front]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Build
|
||||
run: yarn build --projects=@strapi/admin,@strapi/helper-plugin
|
||||
|
||||
e2e:
|
||||
timeout-minutes: 60
|
||||
needs: [changes, lint, typescript, unit_front, build]
|
||||
needs: [changes, build, typescript, unit_front]
|
||||
name: 'e2e (browser: ${{ matrix.project }})'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -164,8 +156,8 @@ jobs:
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright@1.38.1 install --with-deps
|
||||
|
||||
- name: Run build
|
||||
run: yarn nx run-many --target=build --nx-ignore-cycles --skip-nx-cache
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
|
||||
- name: Run E2E tests
|
||||
run: yarn test:e2e --setup --concurrency=1 --project=${{ matrix.project }}
|
||||
@ -180,12 +172,12 @@ jobs:
|
||||
api_ce_pg:
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[CE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
shard: [1/2, 2/2]
|
||||
node: [18, 20]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
@ -212,6 +204,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
with:
|
||||
dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
|
||||
@ -220,13 +214,13 @@ jobs:
|
||||
api_ce_mysql:
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[CE] API Integration (mysql:latest, client: ${{ matrix.db_client }}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
db_client: ['mysql', 'mysql2']
|
||||
shard: [1/2, 2/2]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
mysql:
|
||||
image: bitnami/mysql:latest
|
||||
@ -251,6 +245,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
with:
|
||||
dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
|
||||
@ -259,13 +255,13 @@ jobs:
|
||||
api_ce_mysql_5:
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[CE] API Integration (mysql:5, client: ${{ matrix.db_client }} , node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
db_client: ['mysql', 'mysql2']
|
||||
shard: [1/2, 2/2]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
mysql:
|
||||
image: bitnami/mysql:5.7
|
||||
@ -289,6 +285,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
with:
|
||||
dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
|
||||
@ -297,13 +295,13 @@ jobs:
|
||||
api_ce_sqlite:
|
||||
if: needs.changes.outputs.backend == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[CE] API Integration (sqlite, client: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
sqlite_pkg: ['better-sqlite3', 'sqlite3']
|
||||
shard: [1/2, 2/2]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
@ -311,6 +309,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
env:
|
||||
SQLITE_PKG: ${{ matrix.sqlite_pkg }}
|
||||
@ -321,15 +321,15 @@ jobs:
|
||||
# EE
|
||||
api_ee_pg:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[EE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
|
||||
env:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
shard: [1/2, 2/2]
|
||||
node: [18, 20]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
@ -356,6 +356,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
with:
|
||||
dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
|
||||
@ -364,16 +366,16 @@ jobs:
|
||||
|
||||
api_ee_mysql:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[EE] API Integration (mysql:latest, client: ${{ matrix.db_client }}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
|
||||
env:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
db_client: ['mysql', 'mysql2']
|
||||
shard: [1/2, 2/2]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
mysql:
|
||||
image: bitnami/mysql:latest
|
||||
@ -398,6 +400,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
with:
|
||||
dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi'
|
||||
@ -406,16 +410,16 @@ jobs:
|
||||
|
||||
api_ee_sqlite:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes, lint, typescript, unit_back, unit_front]
|
||||
needs: [changes, build, typescript, unit_back, unit_front]
|
||||
name: '[EE] API Integration (sqlite, client: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})'
|
||||
if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
|
||||
env:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16, 18, 20]
|
||||
node: [18, 20]
|
||||
sqlite_pkg: ['better-sqlite3', 'sqlite3']
|
||||
shard: [1/2, 2/2]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
@ -423,6 +427,8 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
- uses: ./.github/actions/run-api-tests
|
||||
env:
|
||||
SQLITE_PKG: ${{ matrix.sqlite_pkg }}
|
||||
|
||||
@ -46,7 +46,7 @@ The Strapi core team will review your pull request and either merge it, request
|
||||
|
||||
## Contribution Prerequisites
|
||||
|
||||
- You have [Node.js](https://nodejs.org/en/) at version >= v16 and <= v20 and [Yarn](https://yarnpkg.com/en/) at v1.2.0+ installed.
|
||||
- You have [Node.js](https://nodejs.org/en/) at version >= v18 and <= v20 and [Yarn](https://yarnpkg.com/en/) at v1.2.0+ installed.
|
||||
- You are familiar with [Git](https://git-scm.com).
|
||||
|
||||
**Before submitting your pull request** make sure the following requirements are fulfilled:
|
||||
|
||||
@ -94,6 +94,7 @@ Strapi only supports maintenance and LTS versions of Node.js. Please refer to th
|
||||
|
||||
| Strapi Version | Recommended | Minimum |
|
||||
| --------------- | ----------- | ------- |
|
||||
| 4.14.5 and up | 20.x | 18.x |
|
||||
| 4.11.0 and up | 18.x | 16.x |
|
||||
| 4.3.9 to 4.10.x | 18.x | 14.x |
|
||||
| 4.0.x to 4.3.8 | 16.x | 14.x |
|
||||
|
||||
@ -14,3 +14,41 @@ This is an experimental API that is subject to change at any moment, hence why i
|
||||
## Available Commands
|
||||
|
||||
- [plugin:build](build) - Build a plugin for publishing
|
||||
- [plugin:watch](watch) - Watch & compile a plugin in local development
|
||||
|
||||
## Setting up your package
|
||||
|
||||
In order to build/watch/check a plugin you need to have a `package.json` that must contain the following fields:
|
||||
|
||||
- `name`
|
||||
- `version`
|
||||
|
||||
In regards to the export keys of your package.json because a plugin _typically_ has both a server and client
|
||||
side output we recommend doing the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@strapi/plugin",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
"./strapi-admin": {
|
||||
"types": "./dist/admin/index.d.ts",
|
||||
"source": "./admin/src/index.ts",
|
||||
"import": "./dist/admin/index.mjs",
|
||||
"require": "./dist/admin/index.js",
|
||||
"default": "./dist/admin/index.js"
|
||||
},
|
||||
"./strapi-server": {
|
||||
"types": "./dist/server/index.d.ts",
|
||||
"source": "./server/src/index.ts",
|
||||
"import": "./dist/server/index.mjs",
|
||||
"require": "./dist/server/index.js",
|
||||
"default": "./dist/server/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We don't use `main`, `module` or `types` on the root level of the package.json because of the aforementioned reason (plugins don't have one entry).
|
||||
If you've not written your plugin in typescript, you can omit the `types` value of an export map. This is the minimum setup required to build a plugin.
|
||||
|
||||
@ -9,8 +9,7 @@ tags:
|
||||
---
|
||||
|
||||
The `plugin:build` command is used to build plugins in a CJS/ESM compatible format that can be instantly published to NPM.
|
||||
This is done by looking at the export fields of a package.json e.g. `main`, `module`, `types` and `exports`. By using the
|
||||
exports map specifically we can build dual plugins that support a server & client output.
|
||||
This is done by using `pack-up` underneath and a specific configuration, for this command we _do not_ look for a `packup.config` file.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -24,48 +23,14 @@ strapi plugin:build
|
||||
Bundle your strapi plugin for publishing.
|
||||
|
||||
Options:
|
||||
-y, --yes Skip all confirmation prompts (default: false)
|
||||
--force Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.
|
||||
-d, --debug Enable debugging mode with verbose logs (default: false)
|
||||
--silent Don't log anything (default: false)
|
||||
--sourcemap produce sourcemaps (default: false)
|
||||
--minify minify the output (default: false)
|
||||
-h, --help Display help for command
|
||||
```
|
||||
|
||||
## Setting up your package
|
||||
|
||||
In order to build a plugin you need to have a `package.json` that must contain the following fields:
|
||||
|
||||
- `name`
|
||||
- `version`
|
||||
|
||||
In regards to the export keys of your package.json because a plugin _typically_ has both a server and client
|
||||
side output we recommend doing the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@strapi/plugin",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
"./strapi-admin": {
|
||||
"types": "./dist/admin/index.d.ts",
|
||||
"source": "./admin/src/index.ts",
|
||||
"import": "./dist/admin/index.mjs",
|
||||
"require": "./dist/admin/index.js",
|
||||
"default": "./dist/admin/index.js"
|
||||
},
|
||||
"./strapi-server": {
|
||||
"types": "./dist/server/index.d.ts",
|
||||
"source": "./server/src/index.ts",
|
||||
"import": "./dist/server/index.mjs",
|
||||
"require": "./dist/server/index.js",
|
||||
"default": "./dist/server/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We don't use `main`, `module` or `types` on the root level of the package.json because of the aforementioned reason (plugins don't have one entry).
|
||||
If you've not written your plugin in typescript, you can omit the `types` value of an export map. This is the minimum setup required to build a plugin.
|
||||
|
||||
## How it works
|
||||
|
||||
The command sequence can be visualised as follows:
|
||||
@ -73,37 +38,6 @@ The command sequence can be visualised as follows:
|
||||
- Load package.json
|
||||
- Validate that package.json against a `yup` schema
|
||||
- Validate the ordering of an export map if `pkg.exports` is defined
|
||||
- Create a build context, this holds information like:
|
||||
- The transpilation target
|
||||
- The external dependencies (that we don't want to bundle)
|
||||
- Where the output should go e.g. `dist`
|
||||
- The exports we're about to use to create build tasks
|
||||
- Create a list of build tasks based on the `exports` from the build context, these can currently either be `"build:js"` or `"build:dts"`
|
||||
- Pass the build task to a specific task handler e.g. `vite` or `tsc`
|
||||
- Create a set of "bundles" to build ignoring the package.json exports map that is _specifically_ set up for strapi-plugins.
|
||||
- Pass the created config to `pack-up`'s build API.
|
||||
- Finish
|
||||
|
||||
## Transpilation target
|
||||
|
||||
There are three different runtimes available for plugins:
|
||||
|
||||
- `node` which equates to a `node16` target
|
||||
- `web` which equates to a `esnext` target
|
||||
- `*` (universal) which equates to `["last 3 major versions", "Firefox ESR", "last 2 Opera versions", "not dead", "node 16.0.0"]`
|
||||
|
||||
The `node` and `web` targets are specifically used for the export maps with they keys `./strapi-server` and `./strapi-admin` respectively.
|
||||
Any other export map values will be transpiled to the universal target. The universal target can be overwritten by adding the `browserslist`
|
||||
key to your `package.json` (seen below):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@strapi/plugin",
|
||||
"version": "1.0.0",
|
||||
"browserslist": [
|
||||
"last 3 major versions",
|
||||
"Firefox ESR",
|
||||
"last 2 Opera versions",
|
||||
"not dead",
|
||||
"node 16.0.0"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
40
docs/docs/docs/01-core/strapi/commands/plugin/02-watch.md
Normal file
40
docs/docs/docs/01-core/strapi/commands/plugin/02-watch.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: plugin:build
|
||||
description: An in depth look at the plugin:build command of the Strapi CLI
|
||||
tags:
|
||||
- CLI
|
||||
- commands
|
||||
- plugins
|
||||
- building
|
||||
---
|
||||
|
||||
The `plugin:watch` command is used to watch plugin source files and compile them to production viable assets in real-time.
|
||||
This is done by using `pack-up` underneath and a specific configuration, for this command we _do not_ look for a `packup.config` file.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
strapi plugin:watch
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```bash
|
||||
Watch & compile your strapi plugin for local development.
|
||||
|
||||
Options:
|
||||
-d, --debug Enable debugging mode with verbose logs (default: false)
|
||||
--silent Don't log anything (default: false)
|
||||
-h, --help Display help for command
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
The command sequence can be visualised as follows:
|
||||
|
||||
- Load package.json
|
||||
- Validate that package.json against a `yup` schema
|
||||
- Validate the ordering of an export map if `pkg.exports` is defined
|
||||
- Create a set of "bundles" to build ignoring the package.json exports map that is _specifically_ set up for strapi-plugins.
|
||||
- Pass the created config to `pack-up`'s watch API.
|
||||
- Run's indefinitely
|
||||
@ -32,6 +32,8 @@ build();
|
||||
|
||||
```ts
|
||||
interface BuildOptions {
|
||||
configFile: false;
|
||||
config?: Config;
|
||||
cwd?: string;
|
||||
debug?: boolean;
|
||||
minify?: boolean;
|
||||
|
||||
@ -27,6 +27,8 @@ watch();
|
||||
|
||||
```ts
|
||||
interface WatchOptions {
|
||||
configFile: false;
|
||||
config?: Config;
|
||||
cwd?: string;
|
||||
debug?: boolean;
|
||||
silent?: boolean;
|
||||
|
||||
@ -47,6 +47,12 @@ interface Config {
|
||||
* Whether to minify the output or not.
|
||||
*/
|
||||
minify?: boolean;
|
||||
/**
|
||||
* Instead of creating as few chunks as possible, this mode
|
||||
* will create separate chunks for all modules using the original module
|
||||
* names as file names
|
||||
*/
|
||||
preserveModules?: boolean;
|
||||
/**
|
||||
* Whether to generate sourcemaps for the output or not.
|
||||
*/
|
||||
@ -56,6 +62,10 @@ interface Config {
|
||||
* Node.js workers and you want them to be transpiled for the node environment.
|
||||
*/
|
||||
runtime?: Runtime;
|
||||
/**
|
||||
* path to the tsconfig file to use for the bundle.
|
||||
*/
|
||||
tsconfig?: string;
|
||||
}
|
||||
|
||||
interface ConfigBundle {
|
||||
@ -63,6 +73,8 @@ interface ConfigBundle {
|
||||
import?: string;
|
||||
require?: string;
|
||||
runtime?: Runtime;
|
||||
tsconfig?: string;
|
||||
types?: string;
|
||||
}
|
||||
|
||||
type Runtime = '*' | 'node' | 'web';
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"better-sqlite3": "8.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
"test:front:watch": "cross-env IS_EE=true run test:front --watch",
|
||||
"test:front:watch:ce": "cross-env IS_EE=false run test:front --watch",
|
||||
"test:generate-app": "yarn build:ts && node test/scripts/generate-test-app.js",
|
||||
"test:generate-app:no-build": "node test/scripts/generate-test-app.js",
|
||||
"test:ts": "yarn test:ts:packages && yarn test:ts:front && yarn test:ts:back",
|
||||
"test:ts:back": "nx run-many --target=test:ts:back --nx-ignore-cycles",
|
||||
"test:ts:front": "nx run-many --target=test:ts:front --nx-ignore-cycles",
|
||||
@ -137,7 +138,7 @@
|
||||
},
|
||||
"packageManager": "yarn@3.6.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,6 @@
|
||||
"@testing-library/jest-dom": "^5.16.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x"
|
||||
"node": ">=18.0.0 <=20.x.x"
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,23 +10,20 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import Logo from './assets/images/logo-strapi-2022.svg';
|
||||
import { LANGUAGE_LOCAL_STORAGE_KEY } from './components/LanguageProvider';
|
||||
import Providers from './components/Providers';
|
||||
import {
|
||||
HOOKS,
|
||||
INJECTION_ZONES
|
||||
} from './constants';
|
||||
import { Providers } from './components/Providers';
|
||||
import { HOOKS, INJECTION_ZONES } from './constants';
|
||||
import { customFields, Plugin, Reducers } from './core/apis';
|
||||
import { configureStore } from './core/store';
|
||||
import { configureStore } from './core/store/configure';
|
||||
import { basename, createHook } from './core/utils';
|
||||
import favicon from './favicon.png';
|
||||
import App from './pages/App';
|
||||
import languageNativeNames from './translations/languageNativeNames';
|
||||
|
||||
const {
|
||||
INJECT_COLUMN_IN_TABLE,
|
||||
INJECT_COLUMN_IN_TABLE,
|
||||
MUTATE_COLLECTION_TYPES_LINKS,
|
||||
MUTATE_EDIT_VIEW_LAYOUT,
|
||||
MUTATE_SINGLE_TYPES_LINKS
|
||||
MUTATE_SINGLE_TYPES_LINKS,
|
||||
} = HOOKS;
|
||||
|
||||
class StrapiApp {
|
||||
|
||||
184
packages/core/admin/admin/src/components/AuthenticatedApp.tsx
Normal file
184
packages/core/admin/admin/src/components/AuthenticatedApp.tsx
Normal file
@ -0,0 +1,184 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
AppInfoContextValue,
|
||||
AppInfoProvider,
|
||||
auth,
|
||||
LoadingIndicatorPage,
|
||||
useFetchClient,
|
||||
useGuidedTour,
|
||||
} from '@strapi/helper-plugin';
|
||||
import lodashGet from 'lodash/get';
|
||||
import { useQueries } from 'react-query';
|
||||
import lt from 'semver/functions/lt';
|
||||
import valid from 'semver/functions/valid';
|
||||
// TODO: DS add loader
|
||||
|
||||
import packageJSON from '../../../package.json';
|
||||
import { UserEntity } from '../../../shared/entities';
|
||||
import { useConfiguration } from '../hooks/useConfiguration';
|
||||
import { APIResponse, APIResponseUsersLegacy } from '../types/adminAPI';
|
||||
// @ts-expect-error - no types yet.
|
||||
import { getFullName, hashAdminUserEmail } from '../utils';
|
||||
|
||||
import { NpsSurvey } from './NpsSurvey';
|
||||
import { PluginsInitializer } from './PluginsInitializer';
|
||||
import { RBACProvider, Permission } from './RBACProvider';
|
||||
|
||||
const strapiVersion = packageJSON.version;
|
||||
|
||||
const AuthenticatedApp = () => {
|
||||
const { setGuidedTourVisibility } = useGuidedTour();
|
||||
const userInfo = auth.get('userInfo');
|
||||
const userName = userInfo
|
||||
? lodashGet(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname)
|
||||
: null;
|
||||
const [userDisplayName, setUserDisplayName] = React.useState(userName);
|
||||
const [userId, setUserId] = React.useState<string>();
|
||||
const { showReleaseNotification } = useConfiguration();
|
||||
const { get } = useFetchClient();
|
||||
const [
|
||||
{ data: appInfos, status },
|
||||
{ data: tagName, isLoading },
|
||||
{ data: permissions, status: fetchPermissionsStatus, refetch, isFetching },
|
||||
{ data: userRoles },
|
||||
] = useQueries([
|
||||
{
|
||||
queryKey: 'app-infos',
|
||||
async queryFn() {
|
||||
const { data } = await get<
|
||||
APIResponse<
|
||||
Pick<
|
||||
AppInfoContextValue,
|
||||
| 'currentEnvironment'
|
||||
| 'autoReload'
|
||||
| 'communityEdition'
|
||||
| 'dependencies'
|
||||
| 'useYarn'
|
||||
| 'projectId'
|
||||
| 'strapiVersion'
|
||||
| 'nodeVersion'
|
||||
>
|
||||
>
|
||||
>('/admin/information');
|
||||
|
||||
return data.data;
|
||||
},
|
||||
},
|
||||
{
|
||||
queryKey: 'strapi-release',
|
||||
async queryFn() {
|
||||
try {
|
||||
const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const response = (await res.json()) as { tag_name: string | null | undefined };
|
||||
|
||||
if (!response.tag_name) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return response.tag_name;
|
||||
} catch (err) {
|
||||
// Don't throw an error
|
||||
return strapiVersion;
|
||||
}
|
||||
},
|
||||
enabled: showReleaseNotification,
|
||||
initialData: strapiVersion,
|
||||
},
|
||||
{
|
||||
queryKey: 'admin-users-permission',
|
||||
async queryFn() {
|
||||
const { data } = await get<{ data: Permission[] }>('/admin/users/me/permissions');
|
||||
|
||||
return data.data;
|
||||
},
|
||||
initialData: [],
|
||||
},
|
||||
{
|
||||
queryKey: 'user-roles',
|
||||
async queryFn() {
|
||||
const {
|
||||
data: {
|
||||
data: { roles },
|
||||
},
|
||||
} = await get<APIResponseUsersLegacy<UserEntity>>('/admin/users/me');
|
||||
|
||||
return roles;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tagName);
|
||||
|
||||
/**
|
||||
* TODO: does this actually need to be an effect?
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
if (userRoles) {
|
||||
const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
|
||||
|
||||
if (isUserSuperAdmin && appInfos?.autoReload) {
|
||||
setGuidedTourVisibility(true);
|
||||
}
|
||||
}
|
||||
}, [userRoles, appInfos, setGuidedTourVisibility]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const getUserId = async () => {
|
||||
const userId = await hashAdminUserEmail(userInfo);
|
||||
setUserId(userId);
|
||||
};
|
||||
|
||||
getUserId();
|
||||
}, [userInfo]);
|
||||
|
||||
// We don't need to wait for the release query to be fetched before rendering the plugins
|
||||
// however, we need the appInfos and the permissions
|
||||
const shouldShowNotDependentQueriesLoader =
|
||||
isFetching || status === 'loading' || fetchPermissionsStatus === 'loading';
|
||||
|
||||
const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader;
|
||||
|
||||
if (shouldShowLoader) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
// TODO: add error state
|
||||
if (status === 'error') {
|
||||
return <div>error...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppInfoProvider
|
||||
{...appInfos}
|
||||
userId={userId}
|
||||
latestStrapiReleaseTag={tagName}
|
||||
setUserDisplayName={setUserDisplayName}
|
||||
shouldUpdateStrapi={shouldUpdateStrapi}
|
||||
userDisplayName={userDisplayName}
|
||||
>
|
||||
<RBACProvider permissions={permissions ?? []} refetchPermissions={refetch}>
|
||||
<NpsSurvey />
|
||||
<PluginsInitializer />
|
||||
</RBACProvider>
|
||||
</AppInfoProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const checkLatestStrapiVersion = (
|
||||
currentPackageVersion: string,
|
||||
latestPublishedVersion: string = ''
|
||||
): boolean => {
|
||||
if (!valid(currentPackageVersion) || !valid(latestPublishedVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lt(currentPackageVersion, latestPublishedVersion);
|
||||
};
|
||||
|
||||
export { AuthenticatedApp };
|
||||
@ -1,116 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
AppInfoProvider,
|
||||
auth,
|
||||
LoadingIndicatorPage,
|
||||
useGuidedTour,
|
||||
useNotification,
|
||||
} from '@strapi/helper-plugin';
|
||||
import get from 'lodash/get';
|
||||
import { useQueries } from 'react-query';
|
||||
// TODO: DS add loader
|
||||
|
||||
import packageJSON from '../../../../package.json';
|
||||
import { useConfiguration } from '../../hooks/useConfiguration';
|
||||
import { getFullName, hashAdminUserEmail } from '../../utils';
|
||||
import { NpsSurvey } from '../NpsSurvey';
|
||||
import { PluginsInitializer } from '../PluginsInitializer';
|
||||
import RBACProvider from '../RBACProvider';
|
||||
|
||||
import { fetchAppInfo, fetchCurrentUserPermissions, fetchUserRoles } from './utils/api';
|
||||
import { checkLatestStrapiVersion } from './utils/checkLatestStrapiVersion';
|
||||
import { fetchStrapiLatestRelease } from './utils/fetchStrapiLatestRelease';
|
||||
|
||||
const strapiVersion = packageJSON.version;
|
||||
|
||||
const AuthenticatedApp = () => {
|
||||
const { setGuidedTourVisibility } = useGuidedTour();
|
||||
const toggleNotification = useNotification();
|
||||
const userInfo = auth.getUserInfo();
|
||||
const userName = get(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname);
|
||||
const [userDisplayName, setUserDisplayName] = useState(userName);
|
||||
const [userId, setUserId] = useState(null);
|
||||
const { showReleaseNotification } = useConfiguration();
|
||||
const [
|
||||
{ data: appInfos, status },
|
||||
{ data: tagName, isLoading },
|
||||
{ data: permissions, status: fetchPermissionsStatus, refetch, isFetching },
|
||||
{ data: userRoles },
|
||||
] = useQueries([
|
||||
{ queryKey: 'app-infos', queryFn: fetchAppInfo },
|
||||
{
|
||||
queryKey: 'strapi-release',
|
||||
queryFn: () => fetchStrapiLatestRelease(toggleNotification),
|
||||
enabled: showReleaseNotification,
|
||||
initialData: strapiVersion,
|
||||
},
|
||||
{
|
||||
queryKey: 'admin-users-permission',
|
||||
queryFn: fetchCurrentUserPermissions,
|
||||
initialData: [],
|
||||
},
|
||||
{
|
||||
queryKey: 'user-roles',
|
||||
queryFn: fetchUserRoles,
|
||||
},
|
||||
]);
|
||||
|
||||
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tagName);
|
||||
|
||||
/**
|
||||
* TODO: does this actually need to be an effect?
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (userRoles) {
|
||||
const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
|
||||
|
||||
if (isUserSuperAdmin && appInfos?.autoReload) {
|
||||
setGuidedTourVisibility(true);
|
||||
}
|
||||
}
|
||||
}, [userRoles, appInfos, setGuidedTourVisibility]);
|
||||
|
||||
useEffect(() => {
|
||||
const getUserId = async () => {
|
||||
const userId = await hashAdminUserEmail(userInfo);
|
||||
setUserId(userId);
|
||||
};
|
||||
|
||||
getUserId();
|
||||
}, [userInfo]);
|
||||
|
||||
// We don't need to wait for the release query to be fetched before rendering the plugins
|
||||
// however, we need the appInfos and the permissions
|
||||
const shouldShowNotDependentQueriesLoader =
|
||||
isFetching || status === 'loading' || fetchPermissionsStatus === 'loading';
|
||||
|
||||
const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader;
|
||||
|
||||
if (shouldShowLoader) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
// TODO: add error state
|
||||
if (status === 'error') {
|
||||
return <div>error...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppInfoProvider
|
||||
{...appInfos}
|
||||
userId={userId}
|
||||
latestStrapiReleaseTag={tagName}
|
||||
setUserDisplayName={setUserDisplayName}
|
||||
shouldUpdateStrapi={shouldUpdateStrapi}
|
||||
userDisplayName={userDisplayName}
|
||||
>
|
||||
<RBACProvider permissions={permissions} refetchPermissions={refetch}>
|
||||
<NpsSurvey />
|
||||
<PluginsInitializer />
|
||||
</RBACProvider>
|
||||
</AppInfoProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthenticatedApp;
|
||||
@ -1,47 +0,0 @@
|
||||
import { getFetchClient } from '@strapi/helper-plugin';
|
||||
|
||||
const { get } = getFetchClient();
|
||||
|
||||
const fetchAppInfo = async () => {
|
||||
try {
|
||||
const { data, headers } = await get('/admin/information');
|
||||
|
||||
if (!headers['content-type'].includes('application/json')) {
|
||||
throw new Error('Not found');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCurrentUserPermissions = async () => {
|
||||
try {
|
||||
const { data, headers } = await get('/admin/users/me/permissions');
|
||||
|
||||
if (!headers['content-type'].includes('application/json')) {
|
||||
throw new Error('Not found');
|
||||
}
|
||||
|
||||
return data.data;
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchUserRoles = async () => {
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
data: { roles },
|
||||
},
|
||||
} = await get('/admin/users/me');
|
||||
|
||||
return roles;
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export { fetchAppInfo, fetchCurrentUserPermissions, fetchUserRoles };
|
||||
@ -1,13 +0,0 @@
|
||||
import lt from 'semver/functions/lt';
|
||||
import valid from 'semver/functions/valid';
|
||||
|
||||
export const checkLatestStrapiVersion = (
|
||||
currentPackageVersion: string,
|
||||
latestPublishedVersion: string
|
||||
): boolean => {
|
||||
if (!valid(currentPackageVersion) || !valid(latestPublishedVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lt(currentPackageVersion, latestPublishedVersion);
|
||||
};
|
||||
@ -1,19 +0,0 @@
|
||||
import packageJSON from '../../../../../package.json';
|
||||
|
||||
const strapiVersion = packageJSON.version;
|
||||
|
||||
export const fetchStrapiLatestRelease = async () => {
|
||||
try {
|
||||
const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest');
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch latest Strapi version.');
|
||||
}
|
||||
const { tag_name } = await res.json();
|
||||
|
||||
return tag_name;
|
||||
} catch (err) {
|
||||
// Don't throw an error
|
||||
return strapiVersion;
|
||||
}
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { checkLatestStrapiVersion } from '../checkLatestStrapiVersion';
|
||||
|
||||
describe('ADMIN | utils | checkLatestStrapiVersion', () => {
|
||||
it('should return true if the current version is lower than the latest published version', () => {
|
||||
expect(checkLatestStrapiVersion('v3.3.2', 'v3.3.4')).toBeTruthy();
|
||||
expect(checkLatestStrapiVersion('3.3.2', 'v3.3.4')).toBeTruthy();
|
||||
expect(checkLatestStrapiVersion('v3.3.2', '3.3.4')).toBeTruthy();
|
||||
expect(checkLatestStrapiVersion('3.3.2', '3.3.4')).toBeTruthy();
|
||||
});
|
||||
it('should return false if the current version is equal to the latest published version', () => {
|
||||
expect(checkLatestStrapiVersion('3.3.4', 'v3.3.4')).toBeFalsy();
|
||||
expect(checkLatestStrapiVersion('v3.3.4', '3.3.4')).toBeFalsy();
|
||||
expect(checkLatestStrapiVersion('3.3.4', '3.3.4')).toBeFalsy();
|
||||
});
|
||||
it('should return false if the current version is a beta of the next release', () => {
|
||||
expect(checkLatestStrapiVersion('3.4.0-beta.1', 'v3.3.4')).toBeFalsy();
|
||||
expect(checkLatestStrapiVersion('v3.4.0-beta.1', '3.3.4')).toBeFalsy();
|
||||
expect(checkLatestStrapiVersion('3.4.0-beta.1', '3.3.4')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
|
||||
import { ConfigurationContext, ConfigurationContextValue } from '../contexts/configuration';
|
||||
|
||||
export interface ConfigurationProviderProps {
|
||||
interface ConfigurationProviderProps {
|
||||
children: React.ReactNode;
|
||||
authLogo: string;
|
||||
menuLogo: string;
|
||||
@ -65,3 +65,4 @@ const ConfigurationProvider = ({
|
||||
};
|
||||
|
||||
export { ConfigurationProvider };
|
||||
export type { ConfigurationProviderProps };
|
||||
|
||||
@ -127,3 +127,4 @@ const reducer = (state = initialState, action: Action) => {
|
||||
};
|
||||
|
||||
export { LanguageProvider, useLocales, LANGUAGE_LOCAL_STORAGE_KEY };
|
||||
export type { LanguageProviderProps, LocalesContextValue };
|
||||
|
||||
125
packages/core/admin/admin/src/components/Providers.tsx
Normal file
125
packages/core/admin/admin/src/components/Providers.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
AutoReloadOverlayBlockerProvider,
|
||||
CustomFieldsProvider,
|
||||
CustomFieldsProviderProps,
|
||||
LibraryProvider,
|
||||
LibraryProviderProps,
|
||||
NotificationsProvider,
|
||||
OverlayBlockerProvider,
|
||||
StrapiAppProvider,
|
||||
StrapiAppProviderProps,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { AdminContext, AdminContextValue } from '../contexts/admin';
|
||||
|
||||
import { ConfigurationProvider, ConfigurationProviderProps } from './ConfigurationProvider';
|
||||
import { GuidedTourProvider } from './GuidedTour/Provider';
|
||||
import { LanguageProvider, LanguageProviderProps } from './LanguageProvider';
|
||||
import { Theme } from './Theme';
|
||||
import { ThemeToggleProvider, ThemeToggleProviderProps } from './ThemeToggleProvider';
|
||||
|
||||
import type { Store } from '../core/store/configure';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface ProvidersProps
|
||||
extends Pick<ThemeToggleProviderProps, 'themes'>,
|
||||
Pick<LanguageProviderProps, 'messages' | 'localeNames'>,
|
||||
Pick<
|
||||
ConfigurationProviderProps,
|
||||
'authLogo' | 'menuLogo' | 'showReleaseNotification' | 'showTutorials'
|
||||
>,
|
||||
Pick<AdminContextValue, 'getAdminInjectedComponents'>,
|
||||
Pick<CustomFieldsProviderProps, 'customFields'>,
|
||||
Pick<LibraryProviderProps, 'components' | 'fields'>,
|
||||
Pick<
|
||||
StrapiAppProviderProps,
|
||||
| 'getPlugin'
|
||||
| 'menu'
|
||||
| 'plugins'
|
||||
| 'runHookParallel'
|
||||
| 'runHookSeries'
|
||||
| 'runHookWaterfall'
|
||||
| 'settings'
|
||||
> {
|
||||
children: React.ReactNode;
|
||||
store: Store;
|
||||
}
|
||||
|
||||
const Providers = ({
|
||||
authLogo,
|
||||
children,
|
||||
components,
|
||||
customFields,
|
||||
fields,
|
||||
getAdminInjectedComponents,
|
||||
getPlugin,
|
||||
localeNames,
|
||||
menu,
|
||||
menuLogo,
|
||||
messages,
|
||||
plugins,
|
||||
runHookParallel,
|
||||
runHookSeries,
|
||||
runHookWaterfall,
|
||||
settings,
|
||||
showReleaseNotification,
|
||||
showTutorials,
|
||||
store,
|
||||
themes,
|
||||
}: ProvidersProps) => {
|
||||
return (
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<ThemeToggleProvider themes={themes}>
|
||||
<Theme>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AdminContext.Provider value={{ getAdminInjectedComponents }}>
|
||||
<ConfigurationProvider
|
||||
authLogo={authLogo}
|
||||
menuLogo={menuLogo}
|
||||
showReleaseNotification={showReleaseNotification}
|
||||
showTutorials={showTutorials}
|
||||
>
|
||||
<StrapiAppProvider
|
||||
getPlugin={getPlugin}
|
||||
menu={menu}
|
||||
plugins={plugins}
|
||||
runHookParallel={runHookParallel}
|
||||
runHookWaterfall={runHookWaterfall}
|
||||
runHookSeries={runHookSeries}
|
||||
settings={settings}
|
||||
>
|
||||
<LibraryProvider components={components} fields={fields}>
|
||||
<CustomFieldsProvider customFields={customFields}>
|
||||
<AutoReloadOverlayBlockerProvider>
|
||||
<OverlayBlockerProvider>
|
||||
<GuidedTourProvider>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
</GuidedTourProvider>
|
||||
</OverlayBlockerProvider>
|
||||
</AutoReloadOverlayBlockerProvider>
|
||||
</CustomFieldsProvider>
|
||||
</LibraryProvider>
|
||||
</StrapiAppProvider>
|
||||
</ConfigurationProvider>
|
||||
</AdminContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Theme>
|
||||
</ThemeToggleProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { Providers };
|
||||
@ -1,156 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
AutoReloadOverlayBlockerProvider,
|
||||
CustomFieldsProvider,
|
||||
LibraryProvider,
|
||||
NotificationsProvider,
|
||||
OverlayBlockerProvider,
|
||||
StrapiAppProvider,
|
||||
} from '@strapi/helper-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { AdminContext } from '../../contexts/admin';
|
||||
import { ConfigurationProvider } from '../ConfigurationProvider';
|
||||
import { GuidedTourProvider } from '../GuidedTour/Provider';
|
||||
import { LanguageProvider } from '../LanguageProvider';
|
||||
import { Theme } from '../Theme';
|
||||
import { ThemeToggleProvider } from '../ThemeToggleProvider';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Providers = ({
|
||||
authLogo,
|
||||
children,
|
||||
components,
|
||||
customFields,
|
||||
fields,
|
||||
getAdminInjectedComponents,
|
||||
getPlugin,
|
||||
localeNames,
|
||||
menu,
|
||||
menuLogo,
|
||||
messages,
|
||||
plugins,
|
||||
runHookParallel,
|
||||
runHookSeries,
|
||||
runHookWaterfall,
|
||||
settings,
|
||||
showReleaseNotification,
|
||||
showTutorials,
|
||||
store,
|
||||
themes,
|
||||
}) => {
|
||||
return (
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<ThemeToggleProvider themes={themes}>
|
||||
<Theme>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<AdminContext.Provider value={{ getAdminInjectedComponents }}>
|
||||
<ConfigurationProvider
|
||||
authLogo={authLogo}
|
||||
menuLogo={menuLogo}
|
||||
showReleaseNotification={showReleaseNotification}
|
||||
showTutorials={showTutorials}
|
||||
>
|
||||
<StrapiAppProvider
|
||||
getPlugin={getPlugin}
|
||||
menu={menu}
|
||||
plugins={plugins}
|
||||
runHookParallel={runHookParallel}
|
||||
runHookWaterfall={runHookWaterfall}
|
||||
runHookSeries={runHookSeries}
|
||||
settings={settings}
|
||||
>
|
||||
<LibraryProvider components={components} fields={fields}>
|
||||
<CustomFieldsProvider customFields={customFields}>
|
||||
<AutoReloadOverlayBlockerProvider>
|
||||
<OverlayBlockerProvider>
|
||||
<GuidedTourProvider>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
</GuidedTourProvider>
|
||||
</OverlayBlockerProvider>
|
||||
</AutoReloadOverlayBlockerProvider>
|
||||
</CustomFieldsProvider>
|
||||
</LibraryProvider>
|
||||
</StrapiAppProvider>
|
||||
</ConfigurationProvider>
|
||||
</AdminContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Theme>
|
||||
</ThemeToggleProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Providers.propTypes = {
|
||||
authLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
components: PropTypes.object.isRequired,
|
||||
customFields: PropTypes.object.isRequired,
|
||||
fields: PropTypes.object.isRequired,
|
||||
getAdminInjectedComponents: PropTypes.func.isRequired,
|
||||
getPlugin: PropTypes.func.isRequired,
|
||||
localeNames: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||
menu: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
to: PropTypes.string.isRequired,
|
||||
icon: PropTypes.func.isRequired,
|
||||
intlLabel: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
permissions: PropTypes.array,
|
||||
Component: PropTypes.func,
|
||||
})
|
||||
).isRequired,
|
||||
menuLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
|
||||
messages: PropTypes.object.isRequired,
|
||||
plugins: PropTypes.object.isRequired,
|
||||
runHookParallel: PropTypes.func.isRequired,
|
||||
runHookWaterfall: PropTypes.func.isRequired,
|
||||
runHookSeries: PropTypes.func.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
showReleaseNotification: PropTypes.bool.isRequired,
|
||||
showTutorials: PropTypes.bool.isRequired,
|
||||
store: PropTypes.object.isRequired,
|
||||
themes: PropTypes.shape({
|
||||
light: PropTypes.shape({
|
||||
colors: PropTypes.object.isRequired,
|
||||
shadows: PropTypes.object.isRequired,
|
||||
sizes: PropTypes.object.isRequired,
|
||||
zIndices: PropTypes.array.isRequired,
|
||||
spaces: PropTypes.array.isRequired,
|
||||
borderRadius: PropTypes.string.isRequired,
|
||||
mediaQueries: PropTypes.object.isRequired,
|
||||
fontSizes: PropTypes.array.isRequired,
|
||||
lineHeights: PropTypes.array.isRequired,
|
||||
fontWeights: PropTypes.object.isRequired,
|
||||
}).isRequired,
|
||||
dark: PropTypes.shape({
|
||||
colors: PropTypes.object.isRequired,
|
||||
shadows: PropTypes.object.isRequired,
|
||||
sizes: PropTypes.object.isRequired,
|
||||
zIndices: PropTypes.array.isRequired,
|
||||
spaces: PropTypes.array.isRequired,
|
||||
borderRadius: PropTypes.string.isRequired,
|
||||
mediaQueries: PropTypes.object.isRequired,
|
||||
fontSizes: PropTypes.array.isRequired,
|
||||
lineHeights: PropTypes.array.isRequired,
|
||||
fontWeights: PropTypes.object.isRequired,
|
||||
}).isRequired,
|
||||
custom: PropTypes.object,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default Providers;
|
||||
124
packages/core/admin/admin/src/components/RBACProvider.tsx
Normal file
124
packages/core/admin/admin/src/components/RBACProvider.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
LoadingIndicatorPage,
|
||||
Permission,
|
||||
RBACContext,
|
||||
RBACContextValue,
|
||||
} from '@strapi/helper-plugin';
|
||||
import produce from 'immer';
|
||||
|
||||
import { useTypedSelector, useTypedDispatch } from '../core/store/hooks';
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* RBACProvider
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
interface RBACProviderProps {
|
||||
children: React.ReactNode;
|
||||
permissions: Permission[];
|
||||
refetchPermissions: RBACContextValue['refetchPermissions'];
|
||||
}
|
||||
|
||||
const RBACProvider = ({ children, permissions, refetchPermissions }: RBACProviderProps) => {
|
||||
const allPermissions = useTypedSelector((state) => state.rbacProvider.allPermissions);
|
||||
|
||||
const dispatch = useTypedDispatch();
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(setPermissionsAction(permissions));
|
||||
|
||||
return () => {
|
||||
dispatch(resetStoreAction());
|
||||
};
|
||||
}, [permissions, dispatch]);
|
||||
|
||||
if (!allPermissions) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RBACContext.Provider value={{ allPermissions, refetchPermissions }}>
|
||||
{children}
|
||||
</RBACContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* RBACReducer
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
interface RBACState {
|
||||
allPermissions: null | Permission[];
|
||||
collectionTypesRelatedPermissions: Record<string, Record<string, Permission[]>>;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
allPermissions: null,
|
||||
collectionTypesRelatedPermissions: {},
|
||||
};
|
||||
|
||||
const RESET_STORE = 'StrapiAdmin/RBACProvider/RESET_STORE';
|
||||
const SET_PERMISSIONS = 'StrapiAdmin/RBACProvider/SET_PERMISSIONS';
|
||||
|
||||
interface ResetStoreAction {
|
||||
type: typeof RESET_STORE;
|
||||
}
|
||||
|
||||
const resetStoreAction = (): ResetStoreAction => ({ type: RESET_STORE });
|
||||
|
||||
interface SetPermissionsAction {
|
||||
type: typeof SET_PERMISSIONS;
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
const setPermissionsAction = (
|
||||
permissions: SetPermissionsAction['permissions']
|
||||
): SetPermissionsAction => ({
|
||||
type: SET_PERMISSIONS,
|
||||
permissions,
|
||||
});
|
||||
|
||||
type Actions = ResetStoreAction | SetPermissionsAction;
|
||||
|
||||
const RBACReducer = (state: RBACState = initialState, action: Actions) =>
|
||||
produce(state, (draftState) => {
|
||||
switch (action.type) {
|
||||
case SET_PERMISSIONS: {
|
||||
draftState.allPermissions = action.permissions;
|
||||
draftState.collectionTypesRelatedPermissions = action.permissions
|
||||
.filter((perm) => perm.subject)
|
||||
.reduce<Record<string, Record<string, Permission[]>>>((acc, current) => {
|
||||
const { subject, action } = current;
|
||||
|
||||
if (!subject) return acc;
|
||||
|
||||
if (!acc[subject]) {
|
||||
acc[subject] = {};
|
||||
}
|
||||
|
||||
acc[subject] = acc[subject][action]
|
||||
? { ...acc[subject], [action]: [...acc[subject][action], current] }
|
||||
: { ...acc[subject], [action]: [current] };
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
break;
|
||||
}
|
||||
case RESET_STORE: {
|
||||
return initialState;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
export { RBACProvider, RBACReducer, resetStoreAction, setPermissionsAction };
|
||||
export type {
|
||||
RBACState,
|
||||
Actions,
|
||||
RBACProviderProps,
|
||||
ResetStoreAction,
|
||||
SetPermissionsAction,
|
||||
Permission,
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
import { RESET_STORE, SET_PERMISSIONS } from './constants';
|
||||
|
||||
const resetStore = () => ({ type: RESET_STORE });
|
||||
|
||||
const setPermissions = (permissions) => ({
|
||||
type: SET_PERMISSIONS,
|
||||
permissions,
|
||||
});
|
||||
|
||||
export { resetStore, setPermissions };
|
||||
@ -1,2 +0,0 @@
|
||||
export const RESET_STORE = 'StrapiAdmin/RBACProvider/RESET_STORE';
|
||||
export const SET_PERMISSIONS = 'StrapiAdmin/RBACProvider/SET_PERMISSIONS';
|
||||
@ -1,39 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { LoadingIndicatorPage, RBACProviderContext } from '@strapi/helper-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { resetStore, setPermissions } from './actions';
|
||||
|
||||
const RBACProvider = ({ children, permissions, refetchPermissions }) => {
|
||||
const { allPermissions } = useSelector((state) => state.rbacProvider);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setPermissions(permissions));
|
||||
|
||||
return () => {
|
||||
dispatch(resetStore());
|
||||
};
|
||||
}, [permissions, dispatch]);
|
||||
|
||||
if (!allPermissions) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RBACProviderContext.Provider value={{ allPermissions, refetchPermissions }}>
|
||||
{children}
|
||||
</RBACProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
RBACProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
permissions: PropTypes.array.isRequired,
|
||||
refetchPermissions: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default RBACProvider;
|
||||
@ -1,51 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* RBACProvider reducer
|
||||
* The goal of this reducer is to provide
|
||||
* the plugins with an access to the user's permissions
|
||||
* in our middleware system
|
||||
*
|
||||
*/
|
||||
|
||||
import produce from 'immer';
|
||||
|
||||
import { RESET_STORE, SET_PERMISSIONS } from './constants';
|
||||
|
||||
const initialState = {
|
||||
allPermissions: null,
|
||||
collectionTypesRelatedPermissions: {},
|
||||
};
|
||||
|
||||
const reducer = (state = initialState, action) =>
|
||||
// eslint-disable-next-line consistent-return
|
||||
produce(state, (draftState) => {
|
||||
switch (action.type) {
|
||||
case SET_PERMISSIONS: {
|
||||
draftState.allPermissions = action.permissions;
|
||||
draftState.collectionTypesRelatedPermissions = action.permissions
|
||||
.filter((perm) => perm.subject)
|
||||
.reduce((acc, current) => {
|
||||
const { subject, action } = current;
|
||||
|
||||
if (!acc[subject]) {
|
||||
acc[subject] = {};
|
||||
}
|
||||
|
||||
acc[subject] = acc[subject][action]
|
||||
? { ...acc[subject], [action]: [...acc[subject][action], current] }
|
||||
: { ...acc[subject], [action]: [current] };
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
break;
|
||||
}
|
||||
case RESET_STORE: {
|
||||
return initialState;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
export default reducer;
|
||||
export { initialState };
|
||||
@ -48,3 +48,4 @@ const ThemeToggleProvider = ({ children, themes }: ThemeToggleProviderProps) =>
|
||||
};
|
||||
|
||||
export { ThemeToggleProvider };
|
||||
export type { ThemeToggleProviderProps };
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { render, waitFor } from '@tests/utils';
|
||||
|
||||
import AuthenticatedApp from '../index';
|
||||
import { AuthenticatedApp } from '../AuthenticatedApp';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
@ -11,14 +9,14 @@ jest.mock('@strapi/helper-plugin', () => ({
|
||||
*/
|
||||
usePersistentState: jest.fn().mockImplementation(() => [{ enabled: false }, jest.fn()]),
|
||||
auth: {
|
||||
getUserInfo: () => ({ firstname: 'kai', lastname: 'doe', email: 'testemail@strapi.io' }),
|
||||
get: () => ({ firstname: 'kai', lastname: 'doe', email: 'testemail@strapi.io' }),
|
||||
},
|
||||
useGuidedTour: jest.fn(() => ({
|
||||
setGuidedTourVisibility: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../PluginsInitializer', () => ({
|
||||
jest.mock('../PluginsInitializer', () => ({
|
||||
PluginsInitializer() {
|
||||
return <div>PluginsInitializer</div>;
|
||||
},
|
||||
@ -34,10 +32,10 @@ describe('AuthenticatedApp', () => {
|
||||
});
|
||||
|
||||
it('should not crash', async () => {
|
||||
const { queryByText } = render(<AuthenticatedApp />);
|
||||
const { queryByText, getByText } = render(<AuthenticatedApp />);
|
||||
|
||||
await waitFor(() => expect(queryByText(/Loading/)).not.toBeInTheDocument());
|
||||
|
||||
expect(queryByText(/PluginsInitializer/)).toBeInTheDocument();
|
||||
expect(getByText(/PluginsInitializer/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -1,10 +1,15 @@
|
||||
import { fixtures } from '@strapi/admin-test-utils';
|
||||
|
||||
import { resetStore, setPermissions } from '../actions';
|
||||
import rbacProviderReducer, { initialState } from '../reducer';
|
||||
import {
|
||||
Permission,
|
||||
RBACReducer,
|
||||
RBACState,
|
||||
resetStoreAction,
|
||||
setPermissionsAction,
|
||||
} from '../RBACProvider';
|
||||
|
||||
describe('rbacProviderReducer', () => {
|
||||
let state;
|
||||
describe('RBACReducer', () => {
|
||||
let state: RBACState;
|
||||
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
@ -16,24 +21,41 @@ describe('rbacProviderReducer', () => {
|
||||
it('returns the initial state', () => {
|
||||
const expected = state;
|
||||
|
||||
expect(rbacProviderReducer(undefined, {})).toEqual(expected);
|
||||
// @ts-expect-error – testing the default case
|
||||
expect(RBACReducer(undefined, {})).toEqual(expected);
|
||||
});
|
||||
|
||||
describe('resetStore', () => {
|
||||
describe('resetStoreAction', () => {
|
||||
it('should reset the state to its initial value', () => {
|
||||
state.allPermissions = true;
|
||||
state.collectionTypesRelatedPermissions = true;
|
||||
state.allPermissions = [];
|
||||
state.collectionTypesRelatedPermissions = {
|
||||
apple: {},
|
||||
};
|
||||
|
||||
expect(rbacProviderReducer(state, resetStore())).toEqual(initialState);
|
||||
expect(RBACReducer(state, resetStoreAction())).toMatchInlineSnapshot(`
|
||||
{
|
||||
"allPermissions": null,
|
||||
"collectionTypesRelatedPermissions": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPermissions', () => {
|
||||
describe('setPermissionsAction', () => {
|
||||
it('should set the allPermissions value correctly', () => {
|
||||
const permissions = [{ action: 'test', subject: null }];
|
||||
const permissions: Permission[] = [
|
||||
{
|
||||
id: 0,
|
||||
action: 'test',
|
||||
subject: null,
|
||||
conditions: [],
|
||||
properties: {},
|
||||
actionParameters: {},
|
||||
},
|
||||
];
|
||||
const expected = { ...state, allPermissions: permissions };
|
||||
|
||||
expect(rbacProviderReducer(state, setPermissions(permissions))).toEqual(expected);
|
||||
expect(RBACReducer(state, setPermissionsAction(permissions))).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should set the collectionTypesRelatedPermissions correctly', () => {
|
||||
@ -89,7 +111,7 @@ describe('rbacProviderReducer', () => {
|
||||
};
|
||||
|
||||
expect(
|
||||
rbacProviderReducer(state, setPermissions(fixtures.permissions.allPermissions))
|
||||
RBACReducer(state, setPermissionsAction(fixtures.permissions.contentManager))
|
||||
.collectionTypesRelatedPermissions
|
||||
).toEqual(expected);
|
||||
});
|
||||
@ -284,13 +284,13 @@ describe('useBlocksStore', () => {
|
||||
);
|
||||
const link = screen.getByRole('link', 'Some link');
|
||||
|
||||
expect(screen.queryByLabelText(/Delete/i, { selector: 'button' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText(/Edit/i, { selector: 'button' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /Delete/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /Edit/i })).not.toBeInTheDocument();
|
||||
|
||||
await user.click(link);
|
||||
|
||||
expect(screen.queryByLabelText(/Delete/i, { selector: 'button' })).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText(/Edit/i, { selector: 'button' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Delete/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Edit/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link fields to edit when user clicks the edit option and check save button disabled state', async () => {
|
||||
|
||||
@ -2,15 +2,16 @@ import * as React from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Typography,
|
||||
BaseLink,
|
||||
Popover,
|
||||
IconButton,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldInput,
|
||||
Flex,
|
||||
Button,
|
||||
Tooltip,
|
||||
} from '@strapi/design-system';
|
||||
import {
|
||||
Code,
|
||||
@ -255,6 +256,18 @@ Image.propTypes = {
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
// Make sure the tooltip is above the popover
|
||||
const TooltipCustom = styled(Tooltip)`
|
||||
z-index: 6;
|
||||
`;
|
||||
|
||||
// Used for the Edit and Cancel buttons in the link popover
|
||||
const CustomButton = styled(Button)`
|
||||
& > span {
|
||||
line-height: normal;
|
||||
}
|
||||
`;
|
||||
|
||||
const Link = React.forwardRef(({ element, children, ...attributes }, forwardedRef) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const editor = useSlate();
|
||||
@ -376,25 +389,49 @@ const Link = React.forwardRef(({ element, children, ...attributes }, forwardedRe
|
||||
</StyledBaseLink>
|
||||
</Typography>
|
||||
<Flex justifyContent="end" width="100%" gap={2}>
|
||||
<IconButton
|
||||
icon={<Trash />}
|
||||
size="L"
|
||||
variant="danger"
|
||||
onClick={() => removeLink(editor)}
|
||||
label={formatMessage({
|
||||
<TooltipCustom
|
||||
description={formatMessage({
|
||||
id: 'components.Blocks.popover.delete',
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<Pencil />}
|
||||
size="L"
|
||||
onClick={() => setIsEditing(true)}
|
||||
label={formatMessage({
|
||||
>
|
||||
<CustomButton
|
||||
size="S"
|
||||
width="2rem"
|
||||
variant="danger-light"
|
||||
onClick={() => removeLink(editor)}
|
||||
aria-label={formatMessage({
|
||||
id: 'components.Blocks.popover.delete',
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
type="button"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Icon width={3} height={3} as={Trash} />
|
||||
</CustomButton>
|
||||
</TooltipCustom>
|
||||
|
||||
<TooltipCustom
|
||||
description={formatMessage({
|
||||
id: 'components.Blocks.popover.edit',
|
||||
defaultMessage: 'Edit',
|
||||
})}
|
||||
/>
|
||||
>
|
||||
<CustomButton
|
||||
size="S"
|
||||
width="2rem"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsEditing(true)}
|
||||
aria-label={formatMessage({
|
||||
id: 'components.Blocks.popover.edit',
|
||||
defaultMessage: 'Edit',
|
||||
})}
|
||||
type="button"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Icon width={3} height={3} as={Pencil} />
|
||||
</CustomButton>
|
||||
</TooltipCustom>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@ -44,7 +44,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom';
|
||||
|
||||
import { HOOKS } from '../../../constants';
|
||||
import { useTypedSelector } from '../../../core/store';
|
||||
import { useTypedSelector } from '../../../core/store/hooks';
|
||||
import { useAdminUsers } from '../../../hooks/useAdminUsers';
|
||||
import { useEnterprise } from '../../../hooks/useEnterprise';
|
||||
import { InjectionZone } from '../../../shared/components';
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ApiTokenPermissionsContext = createContext({});
|
||||
|
||||
const ApiTokenPermissionsContextProvider = ({ children, ...rest }) => {
|
||||
return (
|
||||
<ApiTokenPermissionsContext.Provider value={rest}>
|
||||
{children}
|
||||
</ApiTokenPermissionsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useApiTokenPermissionsContext = () => useContext(ApiTokenPermissionsContext);
|
||||
|
||||
ApiTokenPermissionsContextProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export {
|
||||
ApiTokenPermissionsContext,
|
||||
ApiTokenPermissionsContextProvider,
|
||||
useApiTokenPermissionsContext,
|
||||
};
|
||||
@ -16,3 +16,4 @@ const AdminContext = React.createContext<AdminContextValue>({
|
||||
const useAdmin = () => React.useContext(AdminContext);
|
||||
|
||||
export { AdminContext, useAdmin };
|
||||
export type { AdminContextValue };
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { Entity } from '@strapi/types';
|
||||
|
||||
interface PseudoEvent {
|
||||
target: { value: string };
|
||||
}
|
||||
|
||||
interface APITokenPermissionsContextProviderProps {
|
||||
selectedAction: string[] | null;
|
||||
routes: string[];
|
||||
selectedActions: string[];
|
||||
data: {
|
||||
allActionsIds: Entity.ID[];
|
||||
permissions: {
|
||||
apiId: string;
|
||||
label: string;
|
||||
controllers: { controller: string; actions: { actionId: string; action: string } }[];
|
||||
}[];
|
||||
};
|
||||
onChange: ({ target: { value } }: PseudoEvent) => void;
|
||||
onChangeSelectAll: ({ target: { value } }: PseudoEvent) => void;
|
||||
setSelectedAction: ({ target: { value } }: PseudoEvent) => void;
|
||||
}
|
||||
|
||||
interface ApiTokenPermissionsContextProviderProps extends APITokenPermissionsContextProviderProps {
|
||||
children: React.ReactNode[];
|
||||
}
|
||||
|
||||
const ApiTokenPermissionsContext = React.createContext<APITokenPermissionsContextProviderProps>({
|
||||
selectedAction: null,
|
||||
routes: [],
|
||||
selectedActions: [],
|
||||
data: {
|
||||
allActionsIds: [],
|
||||
permissions: [],
|
||||
},
|
||||
onChange: () => {},
|
||||
onChangeSelectAll: () => {},
|
||||
setSelectedAction: () => {},
|
||||
});
|
||||
|
||||
const ApiTokenPermissionsContextProvider = ({
|
||||
children,
|
||||
...rest
|
||||
}: ApiTokenPermissionsContextProviderProps) => {
|
||||
return (
|
||||
<ApiTokenPermissionsContext.Provider value={rest}>
|
||||
{children}
|
||||
</ApiTokenPermissionsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useApiTokenPermissionsContext = () => React.useContext(ApiTokenPermissionsContext);
|
||||
|
||||
export {
|
||||
ApiTokenPermissionsContext,
|
||||
ApiTokenPermissionsContextProvider,
|
||||
useApiTokenPermissionsContext,
|
||||
};
|
||||
@ -4,42 +4,28 @@ import {
|
||||
Middleware,
|
||||
Reducer,
|
||||
combineReducers,
|
||||
createSelector,
|
||||
Selector,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { useDispatch, useStore, TypedUseSelectorHook, useSelector } from 'react-redux';
|
||||
|
||||
import { RBACReducer } from '../../components/RBACProvider';
|
||||
// @ts-expect-error no types, yet.
|
||||
import rbacProviderReducer from '../components/RBACProvider/reducer';
|
||||
import rbacManagerReducer from '../../content-manager/hooks/useSyncRbac/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import rbacManagerReducer from '../content-manager/hooks/useSyncRbac/reducer';
|
||||
import cmAppReducer from '../../content-manager/pages/App/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import cmAppReducer from '../content-manager/pages/App/reducer';
|
||||
import editViewLayoutManagerReducer from '../../content-manager/pages/EditViewLayoutManager/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import editViewLayoutManagerReducer from '../content-manager/pages/EditViewLayoutManager/reducer';
|
||||
import listViewReducer from '../../content-manager/pages/ListView/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import listViewReducer from '../content-manager/pages/ListView/reducer';
|
||||
import editViewCrudReducer from '../../content-manager/sharedReducers/crudReducer/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import editViewCrudReducer from '../content-manager/sharedReducers/crudReducer/reducer';
|
||||
// @ts-expect-error no types, yet.
|
||||
import appReducer from '../pages/App/reducer';
|
||||
|
||||
const createReducer = (
|
||||
appReducers: Record<string, Reducer>,
|
||||
asyncReducers: Record<string, Reducer>
|
||||
) => {
|
||||
return combineReducers({
|
||||
...appReducers,
|
||||
...asyncReducers,
|
||||
});
|
||||
};
|
||||
import appReducer from '../../pages/App/reducer';
|
||||
|
||||
/**
|
||||
* @description Static reducers are ones we know, they live in the admin package.
|
||||
*/
|
||||
const staticReducers: Record<string, Reducer> = {
|
||||
const staticReducers = {
|
||||
admin_app: appReducer,
|
||||
rbacProvider: rbacProviderReducer,
|
||||
rbacProvider: RBACReducer,
|
||||
'content-manager_app': cmAppReducer,
|
||||
'content-manager_listView': listViewReducer,
|
||||
'content-manager_rbacManager': rbacManagerReducer,
|
||||
@ -60,8 +46,13 @@ const injectReducerStoreEnhancer: (appReducers: Record<string, Reducer>) => Stor
|
||||
asyncReducers,
|
||||
injectReducer: (key: string, asyncReducer: Reducer) => {
|
||||
asyncReducers[key] = asyncReducer;
|
||||
// @ts-expect-error we dynamically add reducers which makes the types uncomfortable.
|
||||
store.replaceReducer(createReducer(appReducers, asyncReducers));
|
||||
store.replaceReducer(
|
||||
// @ts-expect-error we dynamically add reducers which makes the types uncomfortable.
|
||||
combineReducers({
|
||||
...appReducers,
|
||||
...asyncReducers,
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -74,10 +65,10 @@ const configureStoreImpl = (
|
||||
appMiddlewares: Array<() => Middleware> = [],
|
||||
injectedReducers: Record<string, Reducer> = {}
|
||||
) => {
|
||||
const coreReducers = { ...staticReducers, ...injectedReducers };
|
||||
const coreReducers = { ...staticReducers, ...injectedReducers } as const;
|
||||
|
||||
const store = configureStore({
|
||||
reducer: createReducer(coreReducers, {}),
|
||||
reducer: coreReducers,
|
||||
devTools: process.env.NODE_ENV !== 'production',
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...getDefaultMiddleware(),
|
||||
@ -95,20 +86,6 @@ type Store = ReturnType<typeof configureStoreImpl> & {
|
||||
};
|
||||
|
||||
type RootState = ReturnType<Store['getState']>;
|
||||
type AppDispatch = Store['dispatch'];
|
||||
|
||||
const useTypedDispatch: () => AppDispatch = useDispatch;
|
||||
const useTypedStore = useStore as () => Store;
|
||||
const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
||||
const createTypedSelector = <TResult>(selector: Selector<RootState, TResult>) =>
|
||||
createSelector((state: RootState) => state, selector);
|
||||
|
||||
export {
|
||||
useTypedDispatch,
|
||||
useTypedStore,
|
||||
useTypedSelector,
|
||||
configureStoreImpl as configureStore,
|
||||
createTypedSelector,
|
||||
};
|
||||
export type { RootState };
|
||||
export { configureStoreImpl as configureStore };
|
||||
export type { RootState, Store };
|
||||
15
packages/core/admin/admin/src/core/store/hooks.ts
Normal file
15
packages/core/admin/admin/src/core/store/hooks.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createSelector, Selector } from '@reduxjs/toolkit';
|
||||
import { useDispatch, useStore, TypedUseSelectorHook, useSelector } from 'react-redux';
|
||||
|
||||
import type { RootState, Store } from './configure';
|
||||
|
||||
type AppDispatch = Store['dispatch'];
|
||||
|
||||
const useTypedDispatch: () => AppDispatch = useDispatch;
|
||||
const useTypedStore = useStore as () => Store;
|
||||
const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
||||
const createTypedSelector = <TResult>(selector: Selector<RootState, TResult>) =>
|
||||
createSelector((state: RootState) => state, selector);
|
||||
|
||||
export { useTypedDispatch, useTypedStore, useTypedSelector, createTypedSelector };
|
||||
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { configureStore } from '../../../core/store';
|
||||
import { configureStore } from '../../../core/store/configure';
|
||||
import { useInjectReducer } from '../useInjectReducer';
|
||||
|
||||
const store = configureStore();
|
||||
|
||||
@ -33,10 +33,12 @@ const MarketplacePage = lazy(() =>
|
||||
import(/* webpackChunkName: "Admin_marketplace" */ '../MarketplacePage')
|
||||
);
|
||||
const NotFoundPage = lazy(() =>
|
||||
import(/* webpackChunkName: "Admin_NotFoundPage" */ '../NotFoundPage')
|
||||
import(/* webpackChunkName: "Admin_NotFoundPage" */ '../NotFoundPage').then(({ NotFoundPage }) => ({ default: NotFoundPage }))
|
||||
);
|
||||
const InternalErrorPage = lazy(() =>
|
||||
import(/* webpackChunkName: "Admin_InternalErrorPage" */ '../InternalErrorPage')
|
||||
import(/* webpackChunkName: "Admin_InternalErrorPage" */ '../InternalErrorPage').then(({ InternalErrorPage }) => ({
|
||||
default: InternalErrorPage
|
||||
}))
|
||||
);
|
||||
|
||||
const ProfilePage = lazy(() =>
|
||||
|
||||
@ -27,13 +27,15 @@ import { useConfiguration } from '../../hooks/useConfiguration';
|
||||
import { useEnterprise } from '../../hooks/useEnterprise';
|
||||
import { createRoute, makeUniqueRoutes } from '../../utils';
|
||||
import AuthPage from '../AuthPage';
|
||||
import NotFoundPage from '../NotFoundPage';
|
||||
import { NotFoundPage } from '../NotFoundPage';
|
||||
import UseCasePage from '../UseCasePage';
|
||||
|
||||
import { ROUTES_CE, SET_ADMIN_PERMISSIONS } from './constants';
|
||||
|
||||
const AuthenticatedApp = lazy(() =>
|
||||
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp')
|
||||
import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp').then(
|
||||
(mod) => ({ default: mod.AuthenticatedApp })
|
||||
)
|
||||
);
|
||||
|
||||
function App() {
|
||||
|
||||
@ -4,15 +4,21 @@
|
||||
* This is the page we show when the user gets a 500 error
|
||||
*
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { ContentLayout, EmptyStateLayout, HeaderLayout, Main } from '@strapi/design-system';
|
||||
import { LinkButton, useFocusWhenNavigate } from '@strapi/helper-plugin';
|
||||
import {
|
||||
ContentLayout,
|
||||
EmptyStateLayout,
|
||||
HeaderLayout,
|
||||
LinkButton,
|
||||
Main,
|
||||
} from '@strapi/design-system';
|
||||
import { useFocusWhenNavigate } from '@strapi/helper-plugin';
|
||||
import { ArrowRight, EmptyPictures } from '@strapi/icons';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const InternalErrorPage = () => {
|
||||
export const InternalErrorPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useFocusWhenNavigate();
|
||||
|
||||
return (
|
||||
@ -46,5 +52,3 @@ const InternalErrorPage = () => {
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
export default InternalErrorPage;
|
||||
@ -1,433 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { render } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import InternalErrorPage from '../index';
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const App = (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<IntlProvider locale="en" messages={{}} textComponent="span">
|
||||
<Router history={history}>
|
||||
<InternalErrorPage />
|
||||
</Router>
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
describe('InternalErrorPage', () => {
|
||||
it('renders and matches the snapshot', () => {
|
||||
const {
|
||||
container: { firstChild },
|
||||
} = render(App);
|
||||
|
||||
expect(firstChild).toMatchInlineSnapshot(`
|
||||
.c6 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
color: #666687;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 40px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 40px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background: #ffffff;
|
||||
padding: 64px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c10 {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
background: #4945ff;
|
||||
padding-top: 8px;
|
||||
padding-right: 16px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
border-radius: 4px;
|
||||
border-color: #4945ff;
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c16 > svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.c16 > svg > g,
|
||||
.c16 > svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c16[aria-disabled='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c16:after {
|
||||
-webkit-transition-property: all;
|
||||
transition-property: all;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.c16:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c16:focus-visible:after {
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
border: 2px solid #4945ff;
|
||||
}
|
||||
|
||||
.c11 svg {
|
||||
height: 5.5rem;
|
||||
}
|
||||
|
||||
.c0:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
border: 1px solid #d9d8ff;
|
||||
background: #f0f0ff;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] svg > g,
|
||||
.c17[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active svg > g,
|
||||
.c17[aria-disabled='true']:active svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c17:hover {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.c17:active {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c17:active .c5 {
|
||||
color: #4945ff;
|
||||
}
|
||||
|
||||
.c17:active svg > g,
|
||||
.c17:active svg path {
|
||||
fill: #4945ff;
|
||||
}
|
||||
|
||||
.c17 .c5 {
|
||||
color: #271fe0;
|
||||
}
|
||||
|
||||
.c17 svg > g,
|
||||
.c17 svg path {
|
||||
fill: #271fe0;
|
||||
}
|
||||
|
||||
<main
|
||||
aria-labelledby="title"
|
||||
class="c0"
|
||||
id="main-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
data-strapi-header="true"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<h1
|
||||
class="c5 c6"
|
||||
id="title"
|
||||
>
|
||||
Page not found
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8 c9"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="c10 c11"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1rem"
|
||||
viewBox="0 0 216 120"
|
||||
width="10rem"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
opacity="0.88"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M119 28a7 7 0 1 1 0 14h64a7 7 0 1 1 0 14h22a7 7 0 1 1 0 14h-19a7 7 0 1 0 0 14h6a7 7 0 1 1 0 14h-52a7.024 7.024 0 0 1-1.5-.161A7.024 7.024 0 0 1 137 98H46a7 7 0 1 1 0-14H7a7 7 0 1 1 0-14h40a7 7 0 1 0 0-14H22a7 7 0 1 1 0-14h40a7 7 0 1 1 0-14h57Zm90 56a7 7 0 1 1 0 14 7 7 0 0 1 0-14Z"
|
||||
fill="#D9D8FF"
|
||||
fill-opacity="0.8"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m73.83 102.273-8.621 1.422a4 4 0 0 1-4.518-3.404L49.557 21.069a4 4 0 0 1 3.404-4.518l78.231-10.994a4 4 0 0 1 4.518 3.404c.475 3.377 2.408 16.468 2.572 17.63"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m71.805 98.712-3.696.526a3.618 3.618 0 0 1-4.096-3.085l-9.995-71.925a3.646 3.646 0 0 1 3.097-4.108l71.037-10.096a3.618 3.618 0 0 1 4.097 3.085l.859 6.18 9.205 66.599c.306 2.212-1.219 4.257-3.407 4.566a4.31 4.31 0 0 1-.071.01l-67.03 8.248Z"
|
||||
fill="#F0F0FF"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="m69.278 103.123-4.07.572a4 4 0 0 1-4.517-3.404L49.557 21.069a4 4 0 0 1 3.404-4.518l78.231-10.994a4 4 0 0 1 4.518 3.404l.957 6.808M137.5 20.38l.5 3.12"
|
||||
stroke="#7B79FF"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M164.411 30.299 85.844 22.04a2.74 2.74 0 0 0-2.018.598 2.741 2.741 0 0 0-1.004 1.85l-8.363 79.561c-.079.755.155 1.471.598 2.018a2.74 2.74 0 0 0 1.85 1.004l78.567 8.258a2.739 2.739 0 0 0 2.018-.598 2.741 2.741 0 0 0 1.005-1.849l8.362-79.562a2.743 2.743 0 0 0-.598-2.018 2.74 2.74 0 0 0-1.85-1.004Z"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m92.99 30.585 62.655 6.585a3 3 0 0 1 2.67 3.297l-5.54 52.71a3 3 0 0 1-3.297 2.67L86.823 89.26a3 3 0 0 1-2.67-3.297l5.54-52.71a3 3 0 0 1 3.297-2.67Z"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m92.74 73.878 9.798-6.608a4 4 0 0 1 5.168.594l7.173 7.723a1 1 0 0 0 1.362.096l15.34-12.43a4 4 0 0 1 5.878.936l9.98 15.438 1.434 2.392-.687 8.124a1 1 0 0 1-1.106.91l-56.963-6.329a1 1 0 0 1-.886-1.085l.755-8.199 2.755-1.562Z"
|
||||
fill="#F0F0FF"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M155.514 38.413 92.86 31.828c-.481-.05-.937.098-1.285.38a1.745 1.745 0 0 0-.639 1.177l-5.54 52.71c-.05.48.099.936.38 1.284.282.348.697.589 1.178.64l62.655 6.585a1.747 1.747 0 0 0 1.923-1.558l5.54-52.71a1.75 1.75 0 0 0-1.558-1.923Z"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
d="M104.405 55.917a6 6 0 1 0 1.254-11.934 6 6 0 0 0-1.254 11.934Z"
|
||||
fill="#F0F0FF"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
d="m90.729 75.425 11.809-8.155a4 4 0 0 1 5.168.594l7.173 7.723a1 1 0 0 0 1.362.096l15.34-12.43a4 4 0 0 1 5.878.936l11.064 17.556"
|
||||
stroke="#7B79FF"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<p
|
||||
class="c5 c13"
|
||||
>
|
||||
An error occured
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
aria-current="page"
|
||||
aria-disabled="false"
|
||||
class="c14 c15 c16 c17 active"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
class="c5 c18"
|
||||
>
|
||||
Back to homepage
|
||||
</span>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="c4"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1rem"
|
||||
viewBox="0 0 24 24"
|
||||
width="1rem"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 10.7c0-.11.09-.2.2-.2h18.06l-8.239-8.239a.2.2 0 0 1 0-.282L11.86.14a.2.2 0 0 1 .282 0L23.86 11.86a.2.2 0 0 1 0 .282L12.14 23.86a.2.2 0 0 1-.282 0L10.02 22.02a.2.2 0 0 1 0-.282L18.26 13.5H.2a.2.2 0 0 1-.2-.2v-2.6Z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -4,14 +4,18 @@
|
||||
* This is the page we show when the user visits a url that doesn't have a route
|
||||
*
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { ContentLayout, EmptyStateLayout, HeaderLayout, Main } from '@strapi/design-system';
|
||||
import { LinkButton, useFocusWhenNavigate } from '@strapi/helper-plugin';
|
||||
import {
|
||||
ContentLayout,
|
||||
EmptyStateLayout,
|
||||
HeaderLayout,
|
||||
LinkButton,
|
||||
Main,
|
||||
} from '@strapi/design-system';
|
||||
import { useFocusWhenNavigate } from '@strapi/helper-plugin';
|
||||
import { ArrowRight, EmptyPictures } from '@strapi/icons';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const NoContentType = () => {
|
||||
export const NotFoundPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
useFocusWhenNavigate();
|
||||
|
||||
@ -46,5 +50,3 @@ const NoContentType = () => {
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoContentType;
|
||||
@ -1,433 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||
import { render } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import NotFoundPage from '../index';
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const App = (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<IntlProvider locale="en" messages={{}} textComponent="span">
|
||||
<Router history={history}>
|
||||
<NotFoundPage />
|
||||
</Router>
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
describe('NotFoundPage', () => {
|
||||
it('renders and matches the snapshot', () => {
|
||||
const {
|
||||
container: { firstChild },
|
||||
} = render(App);
|
||||
|
||||
expect(firstChild).toMatchInlineSnapshot(`
|
||||
.c6 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
color: #666687;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 40px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 40px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background: #ffffff;
|
||||
padding: 64px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c10 {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
background: #4945ff;
|
||||
padding-top: 8px;
|
||||
padding-right: 16px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
border-radius: 4px;
|
||||
border-color: #4945ff;
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c16 > svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.c16 > svg > g,
|
||||
.c16 > svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c16[aria-disabled='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c16:after {
|
||||
-webkit-transition-property: all;
|
||||
transition-property: all;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.c16:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c16:focus-visible:after {
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
bottom: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
border: 2px solid #4945ff;
|
||||
}
|
||||
|
||||
.c11 svg {
|
||||
height: 5.5rem;
|
||||
}
|
||||
|
||||
.c0:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
border: 1px solid #d9d8ff;
|
||||
background: #f0f0ff;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] svg > g,
|
||||
.c17[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active .c5 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true']:active svg > g,
|
||||
.c17[aria-disabled='true']:active svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c17:hover {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.c17:active {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c17:active .c5 {
|
||||
color: #4945ff;
|
||||
}
|
||||
|
||||
.c17:active svg > g,
|
||||
.c17:active svg path {
|
||||
fill: #4945ff;
|
||||
}
|
||||
|
||||
.c17 .c5 {
|
||||
color: #271fe0;
|
||||
}
|
||||
|
||||
.c17 svg > g,
|
||||
.c17 svg path {
|
||||
fill: #271fe0;
|
||||
}
|
||||
|
||||
<main
|
||||
aria-labelledby="title"
|
||||
class="c0"
|
||||
id="main-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
data-strapi-header="true"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<h1
|
||||
class="c5 c6"
|
||||
id="title"
|
||||
>
|
||||
Page not found
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8 c9"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="c10 c11"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1rem"
|
||||
viewBox="0 0 216 120"
|
||||
width="10rem"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
opacity="0.88"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M119 28a7 7 0 1 1 0 14h64a7 7 0 1 1 0 14h22a7 7 0 1 1 0 14h-19a7 7 0 1 0 0 14h6a7 7 0 1 1 0 14h-52a7.024 7.024 0 0 1-1.5-.161A7.024 7.024 0 0 1 137 98H46a7 7 0 1 1 0-14H7a7 7 0 1 1 0-14h40a7 7 0 1 0 0-14H22a7 7 0 1 1 0-14h40a7 7 0 1 1 0-14h57Zm90 56a7 7 0 1 1 0 14 7 7 0 0 1 0-14Z"
|
||||
fill="#D9D8FF"
|
||||
fill-opacity="0.8"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m73.83 102.273-8.621 1.422a4 4 0 0 1-4.518-3.404L49.557 21.069a4 4 0 0 1 3.404-4.518l78.231-10.994a4 4 0 0 1 4.518 3.404c.475 3.377 2.408 16.468 2.572 17.63"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m71.805 98.712-3.696.526a3.618 3.618 0 0 1-4.096-3.085l-9.995-71.925a3.646 3.646 0 0 1 3.097-4.108l71.037-10.096a3.618 3.618 0 0 1 4.097 3.085l.859 6.18 9.205 66.599c.306 2.212-1.219 4.257-3.407 4.566a4.31 4.31 0 0 1-.071.01l-67.03 8.248Z"
|
||||
fill="#F0F0FF"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="m69.278 103.123-4.07.572a4 4 0 0 1-4.517-3.404L49.557 21.069a4 4 0 0 1 3.404-4.518l78.231-10.994a4 4 0 0 1 4.518 3.404l.957 6.808M137.5 20.38l.5 3.12"
|
||||
stroke="#7B79FF"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M164.411 30.299 85.844 22.04a2.74 2.74 0 0 0-2.018.598 2.741 2.741 0 0 0-1.004 1.85l-8.363 79.561c-.079.755.155 1.471.598 2.018a2.74 2.74 0 0 0 1.85 1.004l78.567 8.258a2.739 2.739 0 0 0 2.018-.598 2.741 2.741 0 0 0 1.005-1.849l8.362-79.562a2.743 2.743 0 0 0-.598-2.018 2.74 2.74 0 0 0-1.85-1.004Z"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m92.99 30.585 62.655 6.585a3 3 0 0 1 2.67 3.297l-5.54 52.71a3 3 0 0 1-3.297 2.67L86.823 89.26a3 3 0 0 1-2.67-3.297l5.54-52.71a3 3 0 0 1 3.297-2.67Z"
|
||||
fill="#fff"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m92.74 73.878 9.798-6.608a4 4 0 0 1 5.168.594l7.173 7.723a1 1 0 0 0 1.362.096l15.34-12.43a4 4 0 0 1 5.878.936l9.98 15.438 1.434 2.392-.687 8.124a1 1 0 0 1-1.106.91l-56.963-6.329a1 1 0 0 1-.886-1.085l.755-8.199 2.755-1.562Z"
|
||||
fill="#F0F0FF"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M155.514 38.413 92.86 31.828c-.481-.05-.937.098-1.285.38a1.745 1.745 0 0 0-.639 1.177l-5.54 52.71c-.05.48.099.936.38 1.284.282.348.697.589 1.178.64l62.655 6.585a1.747 1.747 0 0 0 1.923-1.558l5.54-52.71a1.75 1.75 0 0 0-1.558-1.923Z"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
d="M104.405 55.917a6 6 0 1 0 1.254-11.934 6 6 0 0 0-1.254 11.934Z"
|
||||
fill="#F0F0FF"
|
||||
stroke="#7B79FF"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
<path
|
||||
d="m90.729 75.425 11.809-8.155a4 4 0 0 1 5.168.594l7.173 7.723a1 1 0 0 0 1.362.096l15.34-12.43a4 4 0 0 1 5.878.936l11.064 17.556"
|
||||
stroke="#7B79FF"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<p
|
||||
class="c5 c13"
|
||||
>
|
||||
Oops! We can't seem to find the page you're looging for...
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
aria-current="page"
|
||||
aria-disabled="false"
|
||||
class="c14 c15 c16 c17 active"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
class="c5 c18"
|
||||
>
|
||||
Back to homepage
|
||||
</span>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="c4"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1rem"
|
||||
viewBox="0 0 24 24"
|
||||
width="1rem"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 10.7c0-.11.09-.2.2-.2h18.06l-8.239-8.239a.2.2 0 0 1 0-.282L11.86.14a.2.2 0 0 1 .282 0L23.86 11.86a.2.2 0 0 1 0 .282L12.14 23.86a.2.2 0 0 1-.282 0L10.02 22.02a.2.2 0 0 1 0-.282L18.26 13.5H.2a.2.2 0 0 1-.2-.2v-2.6Z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { Flex, GridItem, Typography } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/ApiTokenPermissions';
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/apiTokenPermissions';
|
||||
import BoundRoute from '../BoundRoute';
|
||||
|
||||
const ActionBoundRoutes = () => {
|
||||
|
||||
@ -17,7 +17,7 @@ import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/ApiTokenPermissions';
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/apiTokenPermissions';
|
||||
|
||||
import CheckboxWrapper from './CheckBoxWrapper';
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { memo } from 'react';
|
||||
import { Flex, Grid, GridItem, Typography } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/ApiTokenPermissions';
|
||||
import { useApiTokenPermissionsContext } from '../../../../../../../contexts/apiTokenPermissions';
|
||||
import ActionBoundRoutes from '../ActionBoundRoutes';
|
||||
import ContentTypesSection from '../ContenTypesSection';
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { ApiTokenPermissionsContextProvider } from '../../../../../contexts/ApiTokenPermissions';
|
||||
import { ApiTokenPermissionsContextProvider } from '../../../../../contexts/apiTokenPermissions';
|
||||
import { formatAPIErrors } from '../../../../../utils';
|
||||
import { selectAdminPermissions } from '../../../../App/selectors';
|
||||
import { API_TOKEN_TYPE } from '../../../components/Tokens/constants';
|
||||
|
||||
@ -148,7 +148,7 @@
|
||||
"@strapi/strapi": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
"qs": "6.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -82,7 +82,7 @@
|
||||
"@strapi/strapi": "^4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
"styled-components": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@ -3,15 +3,11 @@ import * as React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { useNotification } from '../features/Notifications';
|
||||
import { useRBACProvider } from '../features/RBAC';
|
||||
import { Permission, useRBACProvider } from '../features/RBAC';
|
||||
import { hasPermissions } from '../utils/hasPermissions';
|
||||
|
||||
import { LoadingIndicatorPage } from './LoadingIndicatorPage';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
export interface CheckPagePermissionsProps {
|
||||
children: React.ReactNode;
|
||||
permissions?: Permission[];
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useNotification } from '../features/Notifications';
|
||||
import { useRBACProvider } from '../features/RBAC';
|
||||
import { useRBACProvider, Permission } from '../features/RBAC';
|
||||
import { hasPermissions } from '../utils/hasPermissions';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
// NOTE: this component is very similar to the CheckPagePermissions
|
||||
// except that it does not handle redirections nor loading state
|
||||
|
||||
|
||||
@ -115,3 +115,5 @@ export {
|
||||
useAppInfo,
|
||||
useAppInfos,
|
||||
};
|
||||
|
||||
export type { AppInfoContextValue, AppInfoProviderProps };
|
||||
|
||||
@ -128,3 +128,10 @@ const CustomFieldsProvider = ({ children, customFields }: CustomFieldsProviderPr
|
||||
const useCustomFields = () => React.useContext(CustomFieldsContext);
|
||||
|
||||
export { CustomFieldsContext, CustomFieldsProvider, useCustomFields };
|
||||
export type {
|
||||
CustomFieldsProviderProps,
|
||||
CustomField,
|
||||
CustomFieldComponents,
|
||||
CustomFieldOption,
|
||||
CustomFieldOptions,
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@ import * as React from 'react';
|
||||
* Context
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface LibraryContextValue {
|
||||
interface LibraryContextValue {
|
||||
fields?: Record<string, React.ComponentType>;
|
||||
components?: Record<string, React.ComponentType>;
|
||||
}
|
||||
@ -32,3 +32,4 @@ const LibraryProvider = ({ children, fields, components }: LibraryProviderProps)
|
||||
const useLibrary = () => React.useContext(LibraryContext);
|
||||
|
||||
export { LibraryContext, LibraryProvider, useLibrary };
|
||||
export type { LibraryContextValue, LibraryProviderProps };
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
import type { Entity } from '@strapi/types';
|
||||
import type { QueryObserverBaseResult } from 'react-query';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
/**
|
||||
* This is duplicated from the `@strapi/admin` package.
|
||||
*/
|
||||
type Permission = {
|
||||
id?: Entity.ID;
|
||||
action: string;
|
||||
subject: string | null;
|
||||
actionParameters?: object;
|
||||
properties?: {
|
||||
fields?: string[];
|
||||
locales?: string[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
conditions?: string[];
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Context
|
||||
|
||||
@ -4,10 +4,7 @@ import { LinkProps } from 'react-router-dom';
|
||||
|
||||
import { TranslationMessage } from '../types';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
import type { Permission } from './RBAC';
|
||||
interface MenuItem extends Pick<LinkProps, 'to'> {
|
||||
to: string;
|
||||
icon: React.ElementType;
|
||||
@ -62,7 +59,7 @@ type RunHookWaterfall = <InitialValue, Store>(
|
||||
store: Store
|
||||
) => unknown | Promise<unknown>;
|
||||
|
||||
export interface StrapiAppContextValue {
|
||||
interface StrapiAppContextValue {
|
||||
menu: MenuItem[];
|
||||
plugins: Record<string, Plugin>;
|
||||
settings: Record<string, StrapiAppSetting>;
|
||||
@ -124,3 +121,13 @@ const StrapiAppProvider = ({
|
||||
const useStrapiApp = () => React.useContext(StrapiAppContext);
|
||||
|
||||
export { StrapiAppContext, StrapiAppProvider, useStrapiApp };
|
||||
export type {
|
||||
StrapiAppProviderProps,
|
||||
StrapiAppContextValue,
|
||||
MenuItem,
|
||||
Plugin,
|
||||
StrapiAppSettingLink,
|
||||
StrapiAppSetting,
|
||||
RunHookSeries,
|
||||
RunHookWaterfall,
|
||||
};
|
||||
|
||||
@ -2,15 +2,12 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useQueries } from 'react-query';
|
||||
|
||||
import { useRBACProvider } from '../features/RBAC';
|
||||
import { useRBACProvider, Permission } from '../features/RBAC';
|
||||
|
||||
import { useFetchClient } from './useFetchClient';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
type AllowedActions = Record<string, boolean>;
|
||||
|
||||
export const useRBAC = (
|
||||
@ -71,8 +68,7 @@ export const useRBAC = (
|
||||
data: { data },
|
||||
} = await post<
|
||||
{ data: { data: boolean[] } },
|
||||
AxiosResponse<{ data: { data: boolean[] } }>,
|
||||
{ permissions: Permission[] }
|
||||
AxiosResponse<{ data: { data: boolean[] } }>
|
||||
>('/admin/permissions/check', {
|
||||
permissions: matchingPermissions.map(({ action, subject }) => ({
|
||||
action,
|
||||
|
||||
@ -98,26 +98,16 @@ const auth = {
|
||||
sessionStorage.clear();
|
||||
},
|
||||
|
||||
get<T extends keyof StorageItems>(key: T): StorageItems[T] | string | null {
|
||||
const localStorageItem = localStorage.getItem(key);
|
||||
if (localStorageItem) {
|
||||
get<T extends keyof StorageItems>(key: T): StorageItems[T] | null {
|
||||
const item = localStorage.getItem(key) ?? sessionStorage.getItem(key);
|
||||
if (item) {
|
||||
try {
|
||||
const parsedItem = JSON.parse(localStorageItem);
|
||||
const parsedItem = JSON.parse(item);
|
||||
return parsedItem;
|
||||
} catch (error) {
|
||||
// Failed to parse return the string value
|
||||
return localStorageItem;
|
||||
}
|
||||
}
|
||||
|
||||
const sessionStorageItem = sessionStorage.getItem(key);
|
||||
if (sessionStorageItem) {
|
||||
try {
|
||||
const parsedItem = JSON.parse(sessionStorageItem);
|
||||
return parsedItem;
|
||||
} catch (error) {
|
||||
// Failed to parse return the string value
|
||||
return sessionStorageItem;
|
||||
// @ts-expect-error - this is fine
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { getFetchClient } from './getFetchClient';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
import type { Permission } from '../features/RBAC';
|
||||
import type { GenericAbortSignal } from 'axios';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
const findMatchingPermissions = (userPermissions: Permission[], permissions: Permission[]) =>
|
||||
userPermissions.reduce<Permission[]>((acc, curr) => {
|
||||
const associatedPermission = permissions.find(
|
||||
@ -24,7 +22,7 @@ const formatPermissionsForRequest = (permissions: Permission[]) =>
|
||||
return {};
|
||||
}
|
||||
|
||||
const returnedPermission: Permission = {
|
||||
const returnedPermission: Partial<Permission> = {
|
||||
action: permission.action,
|
||||
};
|
||||
|
||||
|
||||
@ -5,9 +5,7 @@ import {
|
||||
shouldCheckPermissions,
|
||||
} from '../hasPermissions';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
import type { Permission } from '../../features/RBAC';
|
||||
|
||||
const hasPermissionsTestData: Record<string, Record<string, Permission[]>> = {
|
||||
userPermissions: {
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +94,7 @@ Strapi only supports maintenance and LTS versions of Node.js. Please refer to th
|
||||
|
||||
| Strapi Version | Recommended | Minimum |
|
||||
| --------------- | ----------- | ------- |
|
||||
| 4.14.5 and up | 20.x | 18.x |
|
||||
| 4.11.0 and up | 18.x | 16.x |
|
||||
| 4.3.9 to 4.10.x | 18.x | 14.x |
|
||||
| 4.0.x to 4.3.8 | 16.x | 14.x |
|
||||
|
||||
@ -171,7 +171,7 @@
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@ -16,7 +16,7 @@ export default async ({ force, ...opts }: ActionOptions) => {
|
||||
* Notify users this is an experimental command and get them to approve first
|
||||
* this can be opted out by setting the argument --yes
|
||||
*/
|
||||
await notifyExperimentalCommand({ force });
|
||||
await notifyExperimentalCommand('plugin:build', { force });
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
|
||||
103
packages/core/strapi/src/commands/actions/plugin/watch/action.ts
Normal file
103
packages/core/strapi/src/commands/actions/plugin/watch/action.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import boxen from 'boxen';
|
||||
import chalk from 'chalk';
|
||||
import { ConfigBundle, WatchCLIOptions, watch } from '@strapi/pack-up';
|
||||
import { notifyExperimentalCommand } from '../../../utils/helpers';
|
||||
import { createLogger } from '../../../utils/logger';
|
||||
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';
|
||||
|
||||
interface ActionOptions extends WatchCLIOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export default async ({ force, ...opts }: ActionOptions) => {
|
||||
const logger = createLogger({ debug: opts.debug, silent: opts.silent, timestamp: false });
|
||||
try {
|
||||
/**
|
||||
* Notify users this is an experimental command and get them to approve first
|
||||
* this can be opted out by setting the argument --yes
|
||||
*/
|
||||
await notifyExperimentalCommand('plugin:watch', { force });
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
const pkg = await loadPkg({ cwd, logger });
|
||||
const pkgJson = await validatePkg({ pkg });
|
||||
|
||||
if (!pkgJson.exports['./strapi-admin'] && !pkgJson.exports['./strapi-server']) {
|
||||
throw new Error(
|
||||
'You need to have either a strapi-admin or strapi-server export in your package.json'
|
||||
);
|
||||
}
|
||||
|
||||
const bundles: ConfigBundle[] = [];
|
||||
|
||||
if (pkgJson.exports['./strapi-admin']) {
|
||||
const exp = pkgJson.exports['./strapi-admin'] as Export;
|
||||
|
||||
const bundle: ConfigBundle = {
|
||||
source: exp.source,
|
||||
import: exp.import,
|
||||
require: exp.require,
|
||||
runtime: 'web',
|
||||
};
|
||||
|
||||
if (exp.types) {
|
||||
bundle.types = exp.types;
|
||||
// TODO: should this be sliced from the source path...?
|
||||
bundle.tsconfig = './admin/tsconfig.build.json';
|
||||
}
|
||||
|
||||
bundles.push(bundle);
|
||||
}
|
||||
|
||||
if (pkgJson.exports['./strapi-server']) {
|
||||
const exp = pkgJson.exports['./strapi-server'] as Export;
|
||||
|
||||
const bundle: ConfigBundle = {
|
||||
source: exp.source,
|
||||
import: exp.import,
|
||||
require: exp.require,
|
||||
runtime: 'node',
|
||||
};
|
||||
|
||||
if (exp.types) {
|
||||
bundle.types = exp.types;
|
||||
// TODO: should this be sliced from the source path...?
|
||||
bundle.tsconfig = './server/tsconfig.build.json';
|
||||
}
|
||||
|
||||
bundles.push(bundle);
|
||||
}
|
||||
|
||||
await watch({
|
||||
cwd,
|
||||
configFile: false,
|
||||
config: {
|
||||
bundles,
|
||||
dist: './dist',
|
||||
/**
|
||||
* ignore the exports map of a plugin, because we're streamlining the
|
||||
* process and ensuring the server package and admin package are built
|
||||
* with the correct runtime and their individual tsconfigs
|
||||
*/
|
||||
exports: {},
|
||||
},
|
||||
...opts,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
'There seems to be an unexpected error, try again with --debug for more information \n'
|
||||
);
|
||||
if (err instanceof Error && err.stack) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
boxen(err.stack, {
|
||||
padding: 1,
|
||||
align: 'left',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import type { StrapiCommand } from '../../../types';
|
||||
import { runAction } from '../../../utils/helpers';
|
||||
import action from './action';
|
||||
|
||||
/**
|
||||
* `$ strapi plugin:build`
|
||||
*/
|
||||
const command: StrapiCommand = ({ command }) => {
|
||||
command
|
||||
.command('plugin:watch')
|
||||
.description('Watch & compile your strapi plugin for local development.')
|
||||
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
|
||||
.option('--silent', "Don't log anything", false)
|
||||
.action(runAction('plugin:watch', action));
|
||||
};
|
||||
|
||||
export default command;
|
||||
@ -28,6 +28,7 @@ import uninstallCommand from './actions/uninstall/command';
|
||||
import versionCommand from './actions/version/command';
|
||||
import watchAdminCommand from './actions/watch-admin/command';
|
||||
import buildPluginCommand from './actions/plugin/build-command/command';
|
||||
import watchPluginCommand from './actions/plugin/watch/command';
|
||||
|
||||
const strapiCommands = {
|
||||
createAdminUser,
|
||||
@ -58,6 +59,7 @@ const strapiCommands = {
|
||||
versionCommand,
|
||||
watchAdminCommand,
|
||||
buildPluginCommand,
|
||||
watchPluginCommand,
|
||||
} as const;
|
||||
|
||||
const buildStrapiCommand = (argv: string[], command = new Command()) => {
|
||||
|
||||
@ -151,22 +151,20 @@ const runAction =
|
||||
* @description Notify users this is an experimental command and get them to approve first
|
||||
* this can be opted out by passing `yes` as a property of the args object.
|
||||
*
|
||||
* @type {(args?: { force?: boolean }) => Promise<void>}
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { notifyExperimentalCommand } = require('../utils/helpers');
|
||||
*
|
||||
* const myCommand = async ({ force }) => {
|
||||
* await notifyExperimentalCommand({ force });
|
||||
* await notifyExperimentalCommand('plugin:build', { force });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const notifyExperimentalCommand = async ({ force }: { force?: boolean } = {}) => {
|
||||
const notifyExperimentalCommand = async (name: string, { force }: { force?: boolean } = {}) => {
|
||||
console.log(
|
||||
boxen(
|
||||
`The ${chalk.bold(
|
||||
chalk.underline('plugin:build')
|
||||
chalk.underline(name)
|
||||
)} command is considered experimental, use at your own risk.`,
|
||||
{
|
||||
title: 'Warning',
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
"copyfiles": "2.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
node: '>=16.0.0 <=20.x.x',
|
||||
node: '>=18.0.0 <=20.x.x',
|
||||
npm: '>=6.0.0',
|
||||
};
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
"tsconfig": "4.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"strapi-server.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production strapi plugin:build --force",
|
||||
"build": "strapi plugin:build --force",
|
||||
"clean": "run -T rimraf ./dist",
|
||||
"lint": "yarn lint:project && yarn lint:back && yarn lint:front",
|
||||
"lint:back": "run -T eslint ./server -c ./server/.eslintrc.js",
|
||||
@ -54,7 +54,7 @@
|
||||
"test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:watch:ce": "run -T cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
||||
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
||||
"watch": "run -T tsc -w --preserveWatchOutput"
|
||||
"watch": "strapi plugin:watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/design-system": "1.12.2",
|
||||
@ -83,7 +83,7 @@
|
||||
"styled-components": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=20.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user