Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f866529307 | |||
| 99eaec9653 | |||
| 2ab3a67a44 | |||
| 0fd29e82d5 | |||
| 0776b7d95a | |||
| 19f7bb77c0 | |||
| 83cae4036c | |||
| ed07eecad4 | |||
| 46ba75b119 | |||
| 1b4d1043af | |||
| bc458b3f68 | |||
| 5bc845172b | |||
| 8a35e862ff | |||
| bc0dbcb678 | |||
| 68ec85e94a | |||
| ca754d5bcd | |||
| 06df255072 | |||
| da1a9e1d7a | |||
| 74ada8fd6b | |||
| 0c123bb9de | |||
| 2a6367c3ac | |||
| 79bdc6606e | |||
| c53434411d | |||
| b9c69e7c08 | |||
| b1e0a24626 | |||
| a1b58fb864 | |||
| e1a8cc31f9 | |||
| d0e5aa5952 | |||
| 3b58fb7e1e | |||
| 1e02ded7b8 | |||
| 92fcb359c9 | |||
| 8c9b35ec47 | |||
| 4e75d675f8 | |||
| 1d4d40aad0 | |||
| aaa9b27c61 | |||
| a29ef05d8b | |||
| 40c39bd9ba | |||
| 59f9f54b34 | |||
| 7804583632 | |||
| 63b8984eb2 | |||
| 0df8f53037 | |||
| 41084baa0f | |||
| 32de0b9221 | |||
| 1384640a3a | |||
| 569aff05a1 | |||
| b13f4db536 | |||
| 8ace3099b9 | |||
| 38d2125ecf | |||
| 6ae195b90c | |||
| be64abb22f | |||
| ca56eb0cac | |||
| 99ca34719f | |||
| 014fb62f95 | |||
| 99433a6722 | |||
| 5ebc88bcff | |||
| a65e61bd96 | |||
| b8beb56135 |
@@ -20,7 +20,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Create empty source files
|
||||
run: |
|
||||
touch src/backend/locale/django.pot
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
CROWDIN_BASE_PATH: "../src/"
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
@@ -20,19 +20,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
# Backend i18n
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Upgrade pip and setuptools
|
||||
run: pip install --upgrade pip setuptools
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .
|
||||
python-version-file: "src/backend/pyproject.toml"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
- name: Install the project
|
||||
run: uv sync --locked --all-extras
|
||||
working-directory: src/backend
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -45,10 +45,10 @@ jobs:
|
||||
- name: generate pot files
|
||||
working-directory: src/backend
|
||||
run: |
|
||||
DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot
|
||||
DJANGO_CONFIGURATION=Build uv run python manage.py makemessages -a --keep-pot
|
||||
# frontend i18n
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
@@ -20,16 +20,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
- name: Setup Node.js
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
- name: Install dependencies
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: cd src/frontend/ && yarn install --frozen-lockfile
|
||||
- name: Cache install frontend
|
||||
if: steps.front-node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/frontend/**/node_modules"
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
@@ -50,10 +50,10 @@ jobs:
|
||||
working-directory: src/mail
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.mail-templates.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Cache mail templates
|
||||
if: steps.mail-templates.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Call argocd github webhook
|
||||
run: |
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' # Makes sense only for pull requests
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: show
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check that the CHANGELOG has been modified in the current branch
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Check CHANGELOG max line length
|
||||
run: |
|
||||
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
|
||||
@@ -73,10 +73,10 @@ jobs:
|
||||
needs: dependencies
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: 'src/frontend/**/node_modules'
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
run: cd src/frontend/ && yarn ci:build
|
||||
|
||||
- name: Cache build frontend
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: src/frontend/apps/desk/out/
|
||||
key: build-front-${{ github.run_id }}
|
||||
@@ -96,10 +96,10 @@ jobs:
|
||||
needs: dependencies
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: 'src/frontend/**/node_modules'
|
||||
@@ -113,10 +113,10 @@ jobs:
|
||||
needs: dependencies
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: 'src/frontend/**/node_modules'
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
shardTotal: [4]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set services env variables
|
||||
run: |
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
cat env.d/development/common.e2e.dist >> env.d/development/common
|
||||
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
@@ -152,14 +152,14 @@ jobs:
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Restore the frontend cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: front-node_modules
|
||||
with:
|
||||
path: 'src/frontend/**/node_modules'
|
||||
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
|
||||
|
||||
- name: Restore the build cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: cache-build
|
||||
with:
|
||||
path: src/frontend/apps/desk/out/
|
||||
@@ -220,19 +220,22 @@ jobs:
|
||||
working-directory: src/backend
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
python-version-file: "src/backend/pyproject.toml"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
- name: Install the project
|
||||
run: uv sync --locked --all-extras
|
||||
|
||||
- name: Check code formatting with ruff
|
||||
run: ~/.local/bin/ruff format . --diff
|
||||
run: uv run ruff format . --diff
|
||||
- name: Lint code with ruff
|
||||
run: ~/.local/bin/ruff check .
|
||||
run: uv run ruff check .
|
||||
- name: Lint code with pylint
|
||||
run: ~/.local/bin/pylint .
|
||||
run: uv run pylint .
|
||||
|
||||
test-back:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -266,29 +269,35 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Create writable /data
|
||||
run: |
|
||||
sudo mkdir -p /data/media && \
|
||||
sudo mkdir -p /data/static
|
||||
- name: Restore the mail templates
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: mail-templates
|
||||
with:
|
||||
path: "src/backend/core/templates/mail"
|
||||
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.13'
|
||||
- name: Install development dependencies
|
||||
run: pip install --user .[dev]
|
||||
python-version-file: "src/backend/pyproject.toml"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
- name: Install the dependencies
|
||||
run: uv sync --locked --all-extras
|
||||
|
||||
- name: Install gettext (required to compile messages)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gettext
|
||||
|
||||
- name: Generate a MO file from strings extracted from the project
|
||||
run: python manage.py compilemessages
|
||||
run: uv run python manage.py compilemessages
|
||||
|
||||
- name: Run tests
|
||||
run: ~/.local/bin/pytest -n 2
|
||||
run: uv run pytest -n 2
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
+53
-1
@@ -8,6 +8,54 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- ✨(mailboxes) enforce lowercase on mailboxes
|
||||
- 🐛(i18n) fix missing translations for status tag labels
|
||||
- 🚚(route) prioritize mail domains as default landing page
|
||||
|
||||
### Changed
|
||||
|
||||
- 🍱(static) update logo in invitation email template #1085
|
||||
- ✨(uiV2) use Lasuite UI kit, new layout
|
||||
|
||||
## [1.23.1] - 2026-02-16
|
||||
|
||||
- ✨(invitations) refresh expired invitations
|
||||
|
||||
## [1.23.0] - 2026-02-12
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(demo) add aliases to demo #1050
|
||||
- ✨(front) add icon to button to configure a domain
|
||||
- ✨(datagrid) add sort to mailboxes list + mail domain list
|
||||
- ✨(invitations) allow delete invitations mails domains access by an admin
|
||||
- ✨(front) delete invitations mails domains access
|
||||
- ✨(front) add show invitations mails domains access #1040
|
||||
- ✨(invitations) can delete domain invitations
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(domains) fix attemps to send invitations to existing users #953
|
||||
|
||||
### Changed
|
||||
|
||||
- 🚸(email) we should ignore case when looking for existing emails #1056
|
||||
- 🏗️(core) migrate from pip to uv
|
||||
- ✨(front) add show invitations mails domains access #1040
|
||||
|
||||
## [1.22.2] - 2026-01-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(aliases) authorize special domain devnull in alias destinations #1029
|
||||
|
||||
## [1.22.1] - 2026-01-21
|
||||
|
||||
- 🔒️(organization) the first user is not admin #776
|
||||
- 🐛(admin) fix broken alias import #1021
|
||||
|
||||
## [1.22.0] - 2026-01-19
|
||||
|
||||
### Added
|
||||
@@ -475,7 +523,11 @@ and this project adheres to
|
||||
- ✨(domains) create and manage domains on admin + API
|
||||
- ✨(domains) mailbox creation + link to email provisioning API
|
||||
|
||||
[unreleased]: https://github.com/suitenumerique/people/compare/v1.22.0...main
|
||||
[unreleased]: https://github.com/suitenumerique/people/compare/v1.23.1...main
|
||||
[1.23.1]: https://github.com/suitenumerique/people/releases/v1.23.1
|
||||
[1.23.0]: https://github.com/suitenumerique/people/releases/v1.23.0
|
||||
[1.22.2]: https://github.com/suitenumerique/people/releases/v1.22.2
|
||||
[1.22.1]: https://github.com/suitenumerique/people/releases/v1.22.1
|
||||
[1.22.0]: https://github.com/suitenumerique/people/releases/v1.22.0
|
||||
[1.21.0]: https://github.com/suitenumerique/people/releases/v1.21.0
|
||||
[1.20.0]: https://github.com/suitenumerique/people/releases/v1.20.0
|
||||
|
||||
+36
-24
@@ -1,10 +1,7 @@
|
||||
# Django People
|
||||
|
||||
# ---- base image to inherit from ----
|
||||
FROM python:3.13.11-alpine AS base
|
||||
|
||||
# Upgrade pip to its latest release to speed up dependencies installation
|
||||
RUN python -m pip install --upgrade pip
|
||||
FROM python:3.14.2-alpine AS base
|
||||
|
||||
# Upgrade system packages to install security updates
|
||||
RUN apk update && \
|
||||
@@ -13,13 +10,26 @@ RUN apk update && \
|
||||
# ---- Back-end builder image ----
|
||||
FROM base AS back-builder
|
||||
|
||||
WORKDIR /builder
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
|
||||
# Copy required python dependencies
|
||||
COPY ./src/backend /builder
|
||||
# Disable Python downloads, because we want to use the system interpreter
|
||||
# across both images. If using a managed Python version, it needs to be
|
||||
# copied from the build image into the final image;
|
||||
ENV UV_PYTHON_DOWNLOADS=0
|
||||
|
||||
RUN mkdir /install && \
|
||||
pip install --prefix=/install .
|
||||
# install uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.9.10 /uv /uvx /bin/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=src/backend/uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=src/backend/pyproject.toml,target=pyproject.toml \
|
||||
uv sync --locked --no-install-project --no-dev
|
||||
COPY src/backend /app
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --locked --no-dev
|
||||
|
||||
|
||||
# ---- mails ----
|
||||
@@ -42,14 +52,13 @@ RUN apk add \
|
||||
pango \
|
||||
rdfind
|
||||
|
||||
# Copy installed python dependencies
|
||||
COPY --from=back-builder /install /usr/local
|
||||
|
||||
# Copy people application (see .dockerignore)
|
||||
COPY ./src/backend /app/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the application from the builder
|
||||
COPY --from=back-builder /app /app
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# collectstatic
|
||||
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
|
||||
python manage.py collectstatic --noinput
|
||||
@@ -80,14 +89,18 @@ COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
||||
# docker user (see entrypoint).
|
||||
RUN chmod g=u /etc/passwd
|
||||
|
||||
# Copy installed python dependencies
|
||||
COPY --from=back-builder /install /usr/local
|
||||
|
||||
# Copy people application (see .dockerignore)
|
||||
COPY ./src/backend /app/
|
||||
# Copy the application from the builder
|
||||
COPY --from=back-builder /app /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Ensure the uv venv is used at runtime
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Generate compiled translation messages
|
||||
RUN DJANGO_CONFIGURATION=Build \
|
||||
python manage.py compilemessages --ignore=".venv/**/*"
|
||||
|
||||
# We wrap commands run in this container by the following entrypoint that
|
||||
# creates a user on-the-fly with the container user ID (see USER) and root group
|
||||
# ID.
|
||||
@@ -102,10 +115,9 @@ USER root:root
|
||||
# Install psql
|
||||
RUN apk add postgresql-client
|
||||
|
||||
# Uninstall people and re-install it in editable mode along with development
|
||||
# dependencies
|
||||
RUN pip uninstall -y people
|
||||
RUN pip install -e .[dev]
|
||||
# Install development dependencies
|
||||
RUN --mount=from=ghcr.io/astral-sh/uv:0.9.10,source=/uv,target=/bin/uv \
|
||||
uv sync --all-extras --locked
|
||||
|
||||
# Restore the un-privileged user running the application
|
||||
ARG DOCKER_USER
|
||||
|
||||
@@ -107,7 +107,7 @@ bootstrap: \
|
||||
|
||||
# -- Docker/compose
|
||||
build: ## build the app-dev container
|
||||
@$(COMPOSE) build app-dev
|
||||
@$(COMPOSE) build app-dev frontend-dev
|
||||
@$(COMPOSE) build dimail
|
||||
.PHONY: build
|
||||
|
||||
@@ -215,7 +215,7 @@ superuser: ## Create an admin superuser with password "admin"
|
||||
.PHONY: superuser
|
||||
|
||||
back-i18n-compile: ## compile the gettext files
|
||||
@$(MANAGE) compilemessages --ignore="venv/**/*"
|
||||
@$(MANAGE) compilemessages --ignore=".venv/**/*"
|
||||
.PHONY: back-i18n-compile
|
||||
|
||||
back-i18n-generate: ## create the .pot files used for i18n
|
||||
@@ -241,21 +241,21 @@ resetdb: ## flush database and create a superuser "admin"
|
||||
.PHONY: resetdb
|
||||
|
||||
env.d/development/common:
|
||||
cp -n env.d/development/common.dist env.d/development/common
|
||||
cp --update=none env.d/development/common.dist env.d/development/common
|
||||
|
||||
env.d/development/france:
|
||||
cp -n env.d/development/france.dist env.d/development/france
|
||||
cp --update=none env.d/development/france.dist env.d/development/france
|
||||
|
||||
env.d/development/postgresql:
|
||||
cp -n env.d/development/postgresql.dist env.d/development/postgresql
|
||||
cp --update=none env.d/development/postgresql.dist env.d/development/postgresql
|
||||
|
||||
env.d/development/kc_postgresql:
|
||||
cp -n env.d/development/kc_postgresql.dist env.d/development/kc_postgresql
|
||||
cp --update=none env.d/development/kc_postgresql.dist env.d/development/kc_postgresql
|
||||
|
||||
# -- Internationalization
|
||||
|
||||
env.d/development/crowdin:
|
||||
cp -n env.d/development/crowdin.dist env.d/development/crowdin
|
||||
cp --update=none env.d/development/crowdin.dist env.d/development/crowdin
|
||||
|
||||
crowdin-download: ## Download translated message from Crowdin
|
||||
@$(COMPOSE_RUN_CROWDIN) download -c crowdin/config.yml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# People
|
||||
|
||||
People is an application to handle users and teams, and distribute permissions accross [La Suite](https://lasuite.numerique.gouv.fr/).
|
||||
People is an application to handle users and teams, and distribute permissions across [La Suite](https://lasuite.numerique.gouv.fr/).
|
||||
|
||||
It is built on top of [Django Rest
|
||||
Framework](https://www.django-rest-framework.org/).
|
||||
|
||||
@@ -42,6 +42,7 @@ services:
|
||||
- ./src/backend:/app
|
||||
- ./data/media:/data/media
|
||||
- ./data/static:/data/static
|
||||
- /app/.venv
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
@@ -117,6 +118,7 @@ services:
|
||||
- ./src/backend:/app
|
||||
- ./data/media:/data/media
|
||||
- ./data/static:/data/static
|
||||
- /app/.venv
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""People custom admin site."""
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Custom Django admin site application configuration."""
|
||||
|
||||
from django.contrib.admin.apps import AdminConfig
|
||||
|
||||
|
||||
class PeopleAdminConfig(AdminConfig):
|
||||
"""Declare our custom Django admin site."""
|
||||
|
||||
default_site = "admin.sites.PeopleAdminSite"
|
||||
@@ -1,15 +0,0 @@
|
||||
"""Custom Django admin site for the People app."""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class PeopleAdminSite(admin.AdminSite):
|
||||
"""People custom admin site."""
|
||||
|
||||
def each_context(self, request):
|
||||
"""Add custom context to the admin site."""
|
||||
return super().each_context(request) | {
|
||||
"ADMIN_HEADER_BACKGROUND": settings.ADMIN_HEADER_BACKGROUND,
|
||||
"ADMIN_HEADER_COLOR": settings.ADMIN_HEADER_COLOR,
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}
|
||||
<style>
|
||||
html[data-theme="light"], :root {
|
||||
{% if ADMIN_HEADER_BACKGROUND %}--header-bg: {{ ADMIN_HEADER_BACKGROUND }};{% endif %}
|
||||
{% if ADMIN_HEADER_COLOR %}--header-color: {{ ADMIN_HEADER_COLOR }};{% endif %}
|
||||
}
|
||||
ul.messagelist li.info {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -621,6 +621,7 @@ class StatView(views.APIView):
|
||||
status=enums.MailDomainStatusChoices.ENABLED
|
||||
).count(),
|
||||
"mailboxes": domains_models.Mailbox.objects.count(),
|
||||
"aliases": domains_models.Alias.objects.count(),
|
||||
}
|
||||
return response.Response(context)
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ from core.models import (
|
||||
AccountService,
|
||||
Contact,
|
||||
Organization,
|
||||
OrganizationAccess,
|
||||
OrganizationRoleChoices,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -131,15 +129,6 @@ class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend):
|
||||
|
||||
user = super().create_user(claims | {"organization": organization})
|
||||
|
||||
if organization_created:
|
||||
# Warning: we may remove this behavior in the near future when we
|
||||
# add a feature to claim the organization ownership.
|
||||
OrganizationAccess.objects.create(
|
||||
organization=organization,
|
||||
user=user,
|
||||
role=OrganizationRoleChoices.ADMIN,
|
||||
)
|
||||
|
||||
# Initiate the user's profile
|
||||
Contact.objects.create(
|
||||
owner=user,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Custom exceptions for mailbox manager app
|
||||
"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
class EmailAlreadyKnownException(APIException):
|
||||
"""Exception raised when trying to create a user with an already existing email address."""
|
||||
|
||||
status_code = status.HTTP_201_CREATED
|
||||
default_detail = _(
|
||||
"Email already known. Invitation not sent but access created instead."
|
||||
)
|
||||
default_code = "email already known"
|
||||
@@ -11,7 +11,7 @@ def update_team_paths(apps, schema_editor):
|
||||
steplen = 5
|
||||
|
||||
# Initialize NumConv with the specified custom alphabet
|
||||
converter = NumConv(len(alphabet), alphabet)
|
||||
converter = NumConv(alphabet)
|
||||
|
||||
nodes = Team.objects.all().order_by("created_at")
|
||||
for i, node in enumerate(nodes, 1):
|
||||
|
||||
@@ -32,6 +32,7 @@ from timezone_field import TimeZoneField
|
||||
from treebeard.mp_tree import MP_Node, MP_NodeManager
|
||||
|
||||
from core.enums import WebhookProtocolChoices, WebhookStatusChoices
|
||||
from core.exceptions import EmailAlreadyKnownException
|
||||
from core.plugins.registry import registry as plugin_hooks_registry
|
||||
from core.utils.webhooks import webhooks_synchronizer
|
||||
from core.validators import get_field_validators_from_setting
|
||||
@@ -813,7 +814,7 @@ class Team(MP_Node, BaseModel):
|
||||
except AttributeError:
|
||||
try:
|
||||
role = self.accesses.filter(user=user).values("role")[0]["role"]
|
||||
except (TeamAccess.DoesNotExist, IndexError):
|
||||
except TeamAccess.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
is_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
|
||||
@@ -900,7 +901,7 @@ class TeamAccess(BaseModel):
|
||||
role = self._meta.model.objects.filter(
|
||||
team=self.team_id, user=user
|
||||
).values("role")[0]["role"]
|
||||
except (self._meta.model.DoesNotExist, IndexError):
|
||||
except self._meta.model.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
is_team_owner_or_admin = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
|
||||
@@ -996,19 +997,22 @@ class BaseInvitation(BaseModel):
|
||||
super().clean()
|
||||
|
||||
# Check if a user already exists for the provided email
|
||||
if User.objects.filter(email=self.email).exists():
|
||||
raise exceptions.ValidationError(
|
||||
{"email": _("This email is already associated to a registered user.")}
|
||||
)
|
||||
if User.objects.filter(email__iexact=self.email).exists():
|
||||
raise EmailAlreadyKnownException
|
||||
|
||||
def refresh(self):
|
||||
"""A simple way to refresh invitation and move expiration date."""
|
||||
self.clean()
|
||||
self.updated_at = timezone.now()
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
"""Calculate if invitation is still valid or has expired."""
|
||||
if not self.created_at:
|
||||
if not self.updated_at:
|
||||
return None
|
||||
|
||||
validity_duration = timedelta(seconds=settings.INVITATION_VALIDITY_DURATION)
|
||||
return timezone.now() > (self.created_at + validity_duration)
|
||||
return timezone.now() > (self.updated_at + validity_duration)
|
||||
|
||||
def _get_mail_subject(self):
|
||||
"""Get the subject of the invitation."""
|
||||
@@ -1096,7 +1100,7 @@ class Invitation(BaseInvitation):
|
||||
role = self.team.accesses.filter(user=user).values("role")[0][
|
||||
"role"
|
||||
]
|
||||
except (self._meta.model.DoesNotExist, IndexError):
|
||||
except self._meta.model.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
can_delete = role in [RoleChoices.OWNER, RoleChoices.ADMIN]
|
||||
|
||||
@@ -9,7 +9,7 @@ plugins_urlpatterns = []
|
||||
for app in settings.INSTALLED_PLUGINS:
|
||||
try:
|
||||
plugins_urlpatterns.append(path("", include(f"{app}.urls")))
|
||||
except (ImportError, AttributeError):
|
||||
except ImportError, AttributeError:
|
||||
# Skip if app doesn't have urls.py
|
||||
continue
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
@@ -391,6 +391,8 @@ def test_authentication_getter_new_user_with_registration_id_new_organization(
|
||||
assert user.organization.domain_list == expected_domain_list
|
||||
assert user.organization.registration_id_list == expected_registration_id_list
|
||||
|
||||
assert models.OrganizationAccess.objects.filter(user=user).exists() is False
|
||||
|
||||
|
||||
def test_authentication_getter_existing_user_via_email_update_organization(
|
||||
django_assert_num_queries, monkeypatch
|
||||
|
||||
@@ -11,7 +11,6 @@ from rest_framework.test import APIClient
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import factories as domains_factories
|
||||
from mailbox_manager import models as domains_models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -21,18 +20,20 @@ def test_api_stats__anonymous(django_assert_num_queries):
|
||||
|
||||
domains_factories.MailDomainEnabledFactory.create_batch(5)
|
||||
core_factories.TeamFactory.create_batch(3)
|
||||
domains_factories.AliasFactory.create_batch(2)
|
||||
|
||||
# clear cache to allow stats count
|
||||
cache.clear()
|
||||
with django_assert_num_queries(5):
|
||||
with django_assert_num_queries(6):
|
||||
response = APIClient().get("/api/v1.0/stats/")
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json() == {
|
||||
"total_users": 0,
|
||||
"mau": 0,
|
||||
"active_domains": 5,
|
||||
"active_domains": 7,
|
||||
"mailboxes": 0,
|
||||
"teams": 3,
|
||||
"aliases": 2,
|
||||
}
|
||||
# no new request made due to caching
|
||||
with django_assert_num_queries(0):
|
||||
@@ -41,9 +42,10 @@ def test_api_stats__anonymous(django_assert_num_queries):
|
||||
assert response.json() == {
|
||||
"total_users": 0,
|
||||
"mau": 0,
|
||||
"active_domains": 5,
|
||||
"active_domains": 7,
|
||||
"mailboxes": 0,
|
||||
"teams": 3,
|
||||
"aliases": 2,
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +60,9 @@ def test_api_stats__expected_count():
|
||||
|
||||
core_factories.TeamFactory.create_batch(3)
|
||||
domains_factories.MailDomainFactory.create_batch(1)
|
||||
domains_factories.MailDomainEnabledFactory.create_batch(2)
|
||||
domains_factories.MailboxFactory.create_batch(
|
||||
10, domain=domains_models.MailDomain.objects.all()[1]
|
||||
)
|
||||
enabled_domain, _ = domains_factories.MailDomainEnabledFactory.create_batch(2)
|
||||
domains_factories.MailboxFactory.create_batch(10, domain=enabled_domain)
|
||||
domains_factories.AliasFactory.create_batch(2, domain=enabled_domain)
|
||||
|
||||
# clear cache to allow stats count
|
||||
cache.clear()
|
||||
@@ -73,4 +74,5 @@ def test_api_stats__expected_count():
|
||||
"active_domains": 2,
|
||||
"mailboxes": 10,
|
||||
"teams": 3,
|
||||
"aliases": 2,
|
||||
}
|
||||
|
||||
@@ -171,10 +171,11 @@ def test_api_team_invitations__create__cannot_invite_existing_users():
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["email"] == [
|
||||
"This email is already associated to a registered user."
|
||||
]
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert (
|
||||
response.json()["detail"]
|
||||
== "Email already known. Invitation not sent but access created instead."
|
||||
)
|
||||
|
||||
|
||||
def test_api_team_invitations__list__anonymous_user():
|
||||
|
||||
@@ -16,6 +16,7 @@ from faker import Faker
|
||||
from freezegun import freeze_time
|
||||
|
||||
from core import factories, models
|
||||
from core.exceptions import EmailAlreadyKnownException
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -48,6 +49,17 @@ def test_models_invitations_team_required():
|
||||
factories.InvitationFactory(team=None)
|
||||
|
||||
|
||||
def test_models_invitations_email_case_insensitive_duplicate_check():
|
||||
"""The email validation should be case-insensitive when checking for existing users."""
|
||||
# Create a user with a lowercase email
|
||||
factories.UserFactory(email="john.doe@example.com")
|
||||
|
||||
# Try to create an invitation with different case
|
||||
# This should raise the same exception as if the email was exactly the same
|
||||
with pytest.raises(EmailAlreadyKnownException):
|
||||
factories.InvitationFactory(email="John.Doe@Example.COM")
|
||||
|
||||
|
||||
def test_models_invitations_team_should_be_team_instance():
|
||||
"""The "team" field should be a team instance."""
|
||||
with pytest.raises(ValueError, match='Invitation.team" must be a "Team" instance'):
|
||||
|
||||
@@ -5,4 +5,6 @@ NB_OBJECTS = {
|
||||
"teams": 100,
|
||||
"max_users_per_team": 100,
|
||||
"domains": 10,
|
||||
"mailboxes_per_domain": 2,
|
||||
"aliases_per_domain": 2,
|
||||
}
|
||||
|
||||
@@ -288,14 +288,12 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
)
|
||||
queue.flush()
|
||||
|
||||
domains = mailbox_models.MailDomain.objects.all()
|
||||
with Timeit(stdout, "Creating accesses to domains"):
|
||||
domains_ids = list(
|
||||
mailbox_models.MailDomain.objects.values_list("id", flat=True)
|
||||
)
|
||||
for domain_id in domains_ids:
|
||||
for domain in domains:
|
||||
queue.push(
|
||||
mailbox_models.MailDomainAccess(
|
||||
domain_id=domain_id,
|
||||
domain=domain,
|
||||
user_id=random.choice(users_ids),
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
@@ -304,11 +302,8 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
queue.flush()
|
||||
|
||||
with Timeit(stdout, "Creating mailboxes"):
|
||||
domains_ids = list(
|
||||
mailbox_models.MailDomain.objects.values_list("id", flat=True)
|
||||
)
|
||||
for domain_id in domains_ids:
|
||||
for i in range(random.randint(1, 10)):
|
||||
for domain in domains:
|
||||
for i in range(defaults.NB_OBJECTS["mailboxes_per_domain"]):
|
||||
first_name = fake.first_name()
|
||||
last_name = fake.last_name()
|
||||
local_part = f"{first_name.lower()}.{last_name.lower()}{i}"
|
||||
@@ -318,7 +313,7 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
local_part=local_part,
|
||||
domain_id=domain_id,
|
||||
domain=domain,
|
||||
secondary_email=f"{local_part}@example.fr",
|
||||
status=random.choice(MailboxStatusChoices.values),
|
||||
dn_email=local_part,
|
||||
@@ -327,6 +322,19 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
|
||||
queue.flush()
|
||||
|
||||
with Timeit(stdout, "Creating aliases"):
|
||||
for domain in domains:
|
||||
for _i in range(defaults.NB_OBJECTS["aliases_per_domain"]):
|
||||
queue.push(
|
||||
mailbox_models.Alias(
|
||||
local_part=fake.word(),
|
||||
destination=fake.email(),
|
||||
domain=domain,
|
||||
)
|
||||
)
|
||||
|
||||
queue.flush()
|
||||
|
||||
with Timeit(stdout, "Creating specific users"):
|
||||
# ⚠️ Warning: this users also need to be created in the keycloak
|
||||
# realm.json AND the OIDC setting to fallback on user email
|
||||
@@ -361,7 +369,7 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
queue.push(user_with_mail)
|
||||
queue.push(
|
||||
mailbox_models.MailDomainAccess(
|
||||
domain_id=domains_ids[0],
|
||||
domain=domains[0],
|
||||
user_id=user_with_mail.pk,
|
||||
role=role,
|
||||
)
|
||||
@@ -387,7 +395,7 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
)
|
||||
queue.push(
|
||||
mailbox_models.MailDomainAccess(
|
||||
domain_id=domains_ids[0],
|
||||
domain=domains[0],
|
||||
user_id=team_mail_user.pk,
|
||||
role=domain_role,
|
||||
)
|
||||
@@ -414,21 +422,20 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
role=MailDomainRoleChoices.ADMIN,
|
||||
)
|
||||
|
||||
# Many mailboxes domain
|
||||
many_boxes_domain, _created = mailbox_models.MailDomain.objects.get_or_create(
|
||||
name="many-boxes-domain.com",
|
||||
# Many objects domain
|
||||
many_objects_domain, _created = mailbox_models.MailDomain.objects.get_or_create(
|
||||
name="many-objects-domain.com",
|
||||
status=MailDomainStatusChoices.ENABLED,
|
||||
support_email="support@mbd.com",
|
||||
)
|
||||
domain_owner = models.User.objects.get(email="e2e.mail-owner@example.com")
|
||||
mailbox_models.MailDomainAccess.objects.get_or_create(
|
||||
domain=many_boxes_domain,
|
||||
domain=many_objects_domain,
|
||||
user=domain_owner,
|
||||
role=MailDomainRoleChoices.OWNER,
|
||||
)
|
||||
mailbox_models.MailDomainInvitation.objects.create(
|
||||
issuer=domain_owner,
|
||||
domain=many_boxes_domain,
|
||||
domain=many_objects_domain,
|
||||
email="people@people.world",
|
||||
role=MailDomainRoleChoices.OWNER,
|
||||
)
|
||||
@@ -436,15 +443,22 @@ def create_demo(stdout): # pylint: disable=too-many-branches too-many-statement
|
||||
first_name = fake.first_name()
|
||||
last_name = fake.last_name()
|
||||
local_part = f"{first_name.lower()}.{last_name.lower()}"
|
||||
|
||||
mailbox_models.Mailbox.objects.create(
|
||||
domain=many_boxes_domain,
|
||||
domain=many_objects_domain,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
local_part=local_part,
|
||||
secondary_email=f"{local_part}@example.fr",
|
||||
status=random.choice(MailboxStatusChoices.values),
|
||||
password=make_password(None), # unusable password
|
||||
dn_email=f"{local_part}@{many_boxes_domain}",
|
||||
dn_email=f"{local_part}@{many_objects_domain}",
|
||||
)
|
||||
|
||||
mailbox_models.Alias.objects.create(
|
||||
local_part=fake.word(),
|
||||
destination=fake.email(),
|
||||
domain=many_objects_domain,
|
||||
)
|
||||
|
||||
# OIDC configuration
|
||||
|
||||
@@ -20,6 +20,8 @@ TEST_NB_OBJECTS = {
|
||||
"teams": 100,
|
||||
"max_users_per_team": 5,
|
||||
"domains": 10,
|
||||
"mailboxes_per_domain": 2,
|
||||
"aliases_per_domain": 2,
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +35,8 @@ def test_commands_create_demo(settings):
|
||||
call_command("create_demo")
|
||||
|
||||
# Monique Test, Jeanne Test and Jean Something (quick fix for e2e)
|
||||
# 3 user with team rights
|
||||
# 3 user with domain rights
|
||||
# 3 users with team rights
|
||||
# 3 users with domain rights
|
||||
# 3 x 3 user with both rights
|
||||
assert models.User.objects.count() == TEST_NB_OBJECTS["users"] + 3 + 3 + 3 + 9
|
||||
|
||||
@@ -55,6 +57,16 @@ def test_commands_create_demo(settings):
|
||||
== TEST_NB_OBJECTS["domains"] + 3 + 9 + 2
|
||||
)
|
||||
|
||||
# TEST_NB_OBJECTS["domains"]*TEST_NB_OBJECTS["mailboxes_per_domain"] = 20
|
||||
# + 30 in the many-object-domain
|
||||
# + 1 mailbox for user-e2e@example.com
|
||||
# = 51
|
||||
assert mailbox_models.Mailbox.objects.count() == 51
|
||||
|
||||
# TEST_NB_OBJECTS["domains"]*TEST_NB_OBJECTS["mailboxes_per_alias"] = 20
|
||||
# + 30 in the many-object-domain
|
||||
assert mailbox_models.Alias.objects.count() == 50
|
||||
|
||||
|
||||
def test_commands_createsuperuser():
|
||||
"""
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-people\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-19 20:15+0000\n"
|
||||
"PO-Revision-Date: 2026-01-19 20:18\n"
|
||||
"POT-Creation-Date: 2026-02-13 11:10+0000\n"
|
||||
"PO-Revision-Date: 2026-02-16 14:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -17,590 +17,336 @@ msgstr ""
|
||||
"X-Crowdin-File: backend.pot\n"
|
||||
"X-Crowdin-File-ID: 2\n"
|
||||
|
||||
#: build/lib/core/admin.py:62 core/admin.py:62
|
||||
#: core/admin.py:62
|
||||
msgid "Personal info"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:73 core/admin.py:73
|
||||
#: core/admin.py:73
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:85 core/admin.py:85
|
||||
#: core/admin.py:85
|
||||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:124 core/admin.py:124
|
||||
#: core/admin.py:124
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:226 core/admin.py:226
|
||||
#: core/admin.py:226
|
||||
msgid "Run post creation plugins"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/admin.py:234 core/admin.py:234
|
||||
#: core/admin.py:234
|
||||
msgid "Post creation plugins have been run for the selected organizations."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/apps.py:65 core/apps.py:65
|
||||
#: core/apps.py:65
|
||||
msgid "People core application"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:104
|
||||
#: core/authentication/backends.py:104
|
||||
#: core/authentication/backends.py:102
|
||||
msgid "Claims contained no recognizable user identification"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:124
|
||||
#: core/authentication/backends.py:124
|
||||
#: core/authentication/backends.py:122
|
||||
msgid "Claims contained no recognizable organization identification"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:181
|
||||
#: build/lib/core/authentication/backends.py:183
|
||||
#: core/authentication/backends.py:181 core/authentication/backends.py:183
|
||||
#: core/authentication/backends.py:170 core/authentication/backends.py:172
|
||||
msgid "Invalid authorization header."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/authentication/backends.py:188
|
||||
#: core/authentication/backends.py:188
|
||||
#: core/authentication/backends.py:177
|
||||
msgid "Invalid api key."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
#: core/enums.py:24
|
||||
msgid "Failure"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:25 build/lib/mailbox_manager/enums.py:21
|
||||
#: build/lib/mailbox_manager/enums.py:31 core/enums.py:25
|
||||
#: mailbox_manager/enums.py:21 mailbox_manager/enums.py:31
|
||||
#: core/enums.py:25 mailbox_manager/enums.py:21 mailbox_manager/enums.py:31
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:26 core/enums.py:26
|
||||
#: core/enums.py:26
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
#: core/exceptions.py:16
|
||||
msgid "Email already known. Invitation not sent but access created instead."
|
||||
msgstr ""
|
||||
|
||||
#: core/models.py:77
|
||||
msgid "Member"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:77 build/lib/core/models.py:89
|
||||
#: build/lib/mailbox_manager/enums.py:14 core/models.py:77 core/models.py:89
|
||||
#: mailbox_manager/enums.py:14
|
||||
#: core/models.py:78 core/models.py:90 mailbox_manager/enums.py:14
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:78 build/lib/mailbox_manager/enums.py:15
|
||||
#: core/models.py:78 mailbox_manager/enums.py:15
|
||||
#: core/models.py:79 mailbox_manager/enums.py:15
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:101 core/models.py:101
|
||||
#: core/models.py:102
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:102 core/models.py:102
|
||||
#: core/models.py:103
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:108 core/models.py:108
|
||||
#: core/models.py:109
|
||||
msgid "created at"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:109 core/models.py:109
|
||||
#: core/models.py:110
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:114 core/models.py:114
|
||||
#: core/models.py:115
|
||||
msgid "updated at"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:115 core/models.py:115
|
||||
#: core/models.py:116
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
#: core/models.py:155
|
||||
msgid "full name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
#: core/models.py:156
|
||||
msgid "short name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:158 core/models.py:158
|
||||
#: core/models.py:159
|
||||
msgid "notes"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: core/models.py:161
|
||||
msgid "contact information"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: core/models.py:162
|
||||
msgid "A JSON object containing the contact information"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: core/models.py:176
|
||||
msgid "contact"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
#: core/models.py:177
|
||||
msgid "contacts"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:250 build/lib/core/models.py:364
|
||||
#: build/lib/core/models.py:510 build/lib/core/models.py:1121
|
||||
#: build/lib/mailbox_manager/models.py:54 core/models.py:250 core/models.py:364
|
||||
#: core/models.py:510 core/models.py:1121 mailbox_manager/models.py:54
|
||||
#: core/models.py:251 core/models.py:365 core/models.py:511 core/models.py:1125
|
||||
#: mailbox_manager/models.py:56
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:252 core/models.py:252
|
||||
#: core/models.py:253
|
||||
msgid "audience id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
#: core/models.py:258
|
||||
msgid "service provider"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
#: core/models.py:259
|
||||
msgid "service providers"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:372 core/models.py:372
|
||||
#: core/models.py:373
|
||||
msgid "registration ID list"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: core/models.py:380
|
||||
msgid "domain list"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:386 core/models.py:386
|
||||
#: core/models.py:387
|
||||
msgid "metadata"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:387 core/models.py:387
|
||||
#: core/models.py:388
|
||||
msgid "A JSON object containing the organization metadata"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:535 core/models.py:397
|
||||
#: core/models.py:535
|
||||
#: core/models.py:398 core/models.py:536
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:403 core/models.py:403
|
||||
#: core/models.py:404
|
||||
msgid "organization"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
#: core/models.py:405
|
||||
msgid "organizations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
#: core/models.py:412
|
||||
msgid "An organization must have at least a registration ID or a domain."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:495 core/models.py:495
|
||||
#: core/models.py:496
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_ characters."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:501 core/models.py:501
|
||||
#: core/models.py:502
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:503 core/models.py:503
|
||||
#: core/models.py:504
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ characters only."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:509 build/lib/core/models.py:973 core/models.py:509
|
||||
#: core/models.py:973
|
||||
#: core/models.py:510 core/models.py:974
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:515 core/models.py:515
|
||||
#: core/models.py:516
|
||||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:516 core/models.py:516
|
||||
#: core/models.py:517
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:522 core/models.py:522
|
||||
#: core/models.py:523
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:525 core/models.py:525
|
||||
#: core/models.py:526
|
||||
msgid "device"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:527 core/models.py:527
|
||||
#: core/models.py:528
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:530 core/models.py:530
|
||||
#: core/models.py:531
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:532 core/models.py:532
|
||||
#: core/models.py:533
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:538 core/models.py:538
|
||||
#: core/models.py:539
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:557 core/models.py:557
|
||||
#: core/models.py:558
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:558 core/models.py:558
|
||||
#: core/models.py:559
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: core/models.py:697
|
||||
msgid "Organization/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:697 core/models.py:697
|
||||
#: core/models.py:698
|
||||
msgid "Organization/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:702 core/models.py:702
|
||||
#: core/models.py:703
|
||||
msgid "This user is already in this organization."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
#: core/models.py:762
|
||||
msgid "external_id"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
#: core/models.py:763
|
||||
msgid "Team external UUID for synchronization with external systems"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:787 core/models.py:787
|
||||
#: core/models.py:788
|
||||
msgid "is visible for all SP"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:789 core/models.py:789
|
||||
#: core/models.py:790
|
||||
msgid "Whether this team is visible to all service providers."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:797 core/models.py:797
|
||||
#: core/models.py:798
|
||||
msgid "Team"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:798 core/models.py:798
|
||||
#: core/models.py:799
|
||||
msgid "Teams"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:849 core/models.py:849
|
||||
#: core/models.py:850
|
||||
msgid "Team/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:850 core/models.py:850
|
||||
#: core/models.py:851
|
||||
msgid "Team/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: core/models.py:856
|
||||
msgid "This user is already in this team."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:941 core/models.py:941
|
||||
#: core/models.py:942
|
||||
msgid "url"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:942 core/models.py:942
|
||||
#: core/models.py:943
|
||||
msgid "secret"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:956 core/models.py:956
|
||||
#: core/models.py:957
|
||||
msgid "Team webhook"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:957 core/models.py:957
|
||||
#: core/models.py:958
|
||||
msgid "Team webhooks"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1015 core/models.py:1015
|
||||
#: core/models.py:1019
|
||||
msgid "Invitation to join La Régie!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1061 core/models.py:1061
|
||||
#: core/models.py:1065
|
||||
msgid "Team invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1062 core/models.py:1062
|
||||
#: core/models.py:1066
|
||||
msgid "Team invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
#: core/models.py:1079
|
||||
#, python-format
|
||||
msgid "[La Suite] You have been invited to become a %(role)s of a group"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1123 core/models.py:1123
|
||||
#: core/models.py:1127
|
||||
msgid "api key"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1128 core/models.py:1128
|
||||
#: core/models.py:1132
|
||||
msgid "allowed scopes"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1129 core/models.py:1129
|
||||
#: core/models.py:1133
|
||||
msgid "Allowed scopes for this service"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1134 core/models.py:1134
|
||||
#: core/models.py:1138
|
||||
msgid "Account service"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1135 core/models.py:1135
|
||||
#: core/models.py:1139
|
||||
msgid "Account services"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:14 mailbox_manager/admin.py:14
|
||||
msgid "Import emails from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:32 build/lib/mailbox_manager/admin.py:71
|
||||
#: mailbox_manager/admin.py:32 mailbox_manager/admin.py:71
|
||||
#, python-format
|
||||
msgid "Synchronisation failed for %(domain)s with message: %(err)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:39 mailbox_manager/admin.py:39
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. Imported mailboxes: %(mailboxes)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:46 build/lib/mailbox_manager/admin.py:90
|
||||
#: mailbox_manager/admin.py:46 mailbox_manager/admin.py:90
|
||||
#, python-format
|
||||
msgid "Sync require enabled domains. Excluded domains: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:51 mailbox_manager/admin.py:51
|
||||
msgid "Import aliases from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:78 mailbox_manager/admin.py:78
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. %(imported_aliases)imported aliases: %(mailboxes)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:95 mailbox_manager/admin.py:95
|
||||
msgid "Check and update status from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:112 mailbox_manager/admin.py:112
|
||||
#, python-format
|
||||
msgid "- %(domain)s with message: %(err)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:125 mailbox_manager/admin.py:125
|
||||
msgid "Check domains done with success."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:126 mailbox_manager/admin.py:126
|
||||
#, python-format
|
||||
msgid "Domains updated: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:128 mailbox_manager/admin.py:128
|
||||
msgid "No domain updated."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:135 mailbox_manager/admin.py:135
|
||||
msgid "Check domain failed for:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:143 mailbox_manager/admin.py:143
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from check: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:148 mailbox_manager/admin.py:148
|
||||
msgid "Fetch domain expected config from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:162 mailbox_manager/admin.py:162
|
||||
#, python-format
|
||||
msgid "Domain expected config fetched with success for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:168 mailbox_manager/admin.py:168
|
||||
#, python-format
|
||||
msgid "Failed to fetch domain expected config for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:174 mailbox_manager/admin.py:174
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from fetch: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:179 mailbox_manager/admin.py:179
|
||||
msgid "Send pending mailboxes to dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:195 mailbox_manager/admin.py:195
|
||||
#, python-format
|
||||
msgid "Failed to send the following mailboxes : %(mailboxes)s."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:201 mailbox_manager/admin.py:201
|
||||
#, python-format
|
||||
msgid "Pending mailboxes successfully sent for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:207 mailbox_manager/admin.py:207
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from : %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/apps.py:11 mailbox_manager/apps.py:11
|
||||
msgid "Mailbox manager"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:13 mailbox_manager/enums.py:13
|
||||
msgid "Viewer"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:22 build/lib/mailbox_manager/enums.py:32
|
||||
#: mailbox_manager/enums.py:22 mailbox_manager/enums.py:32
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:23 build/lib/mailbox_manager/enums.py:33
|
||||
#: mailbox_manager/enums.py:23 mailbox_manager/enums.py:33
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:24 build/lib/mailbox_manager/enums.py:34
|
||||
#: mailbox_manager/enums.py:24 mailbox_manager/enums.py:34
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:25 mailbox_manager/enums.py:25
|
||||
msgid "Action required"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:33 mailbox_manager/models.py:33
|
||||
msgid "[La Suite] Your domain is ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:38 mailbox_manager/models.py:38
|
||||
msgid "[La Suite] Your domain requires action"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:43 mailbox_manager/models.py:43
|
||||
msgid "[La Suite] Your domain has failed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:69 mailbox_manager/models.py:69
|
||||
msgid "support email"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:73 mailbox_manager/models.py:73
|
||||
msgid "last check details"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:74 mailbox_manager/models.py:74
|
||||
msgid "A JSON object containing the last health check details"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:79 mailbox_manager/models.py:79
|
||||
msgid "expected config"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:80 mailbox_manager/models.py:80
|
||||
msgid "A JSON object containing the expected config"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:85 mailbox_manager/models.py:85
|
||||
msgid "Mail domain"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:86 mailbox_manager/models.py:86
|
||||
msgid "Mail domains"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:202 mailbox_manager/models.py:202
|
||||
msgid "User/mail domain relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:203 mailbox_manager/models.py:203
|
||||
msgid "User/mail domain relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:276 mailbox_manager/models.py:276
|
||||
msgid "local_part"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:290 mailbox_manager/models.py:290
|
||||
msgid "secondary email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:301 mailbox_manager/models.py:301
|
||||
msgid "email"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:307 mailbox_manager/models.py:307
|
||||
msgid "Mailbox"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:308 mailbox_manager/models.py:308
|
||||
msgid "Mailboxes"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:350 mailbox_manager/models.py:350
|
||||
msgid "You can't create or update a mailbox for a disabled domain."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:407 mailbox_manager/models.py:407
|
||||
msgid "Mail domain invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:408 mailbox_manager/models.py:408
|
||||
msgid "Mail domain invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:420 mailbox_manager/models.py:420
|
||||
msgid "[La Suite] You have been invited to join La Régie"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:463 mailbox_manager/models.py:463
|
||||
msgid "destination address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:474 mailbox_manager/models.py:474
|
||||
msgid "Alias"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:475 mailbox_manager/models.py:475
|
||||
msgid "Aliases"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/utils/dimail.py:296
|
||||
#: mailbox_manager/utils/dimail.py:296
|
||||
msgid "Your new mailbox information"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/utils/dimail.py:307
|
||||
#: mailbox_manager/utils/dimail.py:307
|
||||
msgid "Your password has been updated"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/people/settings.py:159 people/settings.py:159
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/people/settings.py:160 people/settings.py:160
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: core/templates/mail/html/maildomain_action_required.html:195
|
||||
#: core/templates/mail/text/maildomain_action_required.txt:5
|
||||
msgid "Some actions are required on your domain"
|
||||
@@ -892,3 +638,236 @@ msgstr ""
|
||||
msgid "For more information: Visit the website for La Suite numérique [%(link)s] to discover what tools we offer."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:14
|
||||
msgid "Import emails from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:32 mailbox_manager/admin.py:71
|
||||
#, python-format
|
||||
msgid "Synchronisation failed for %(domain)s with message: %(err)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:39
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. Imported mailboxes: %(mailboxes)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:46 mailbox_manager/admin.py:91
|
||||
#, python-format
|
||||
msgid "Sync require enabled domains. Excluded domains: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:51
|
||||
msgid "Import aliases from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:78
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s.Imported %(count_imported)s aliases: %(aliases)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:96
|
||||
msgid "Check and update status from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:113
|
||||
#, python-format
|
||||
msgid "- %(domain)s with message: %(err)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:126
|
||||
msgid "Check domains done with success."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:127
|
||||
#, python-format
|
||||
msgid "Domains updated: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:129
|
||||
msgid "No domain updated."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:136
|
||||
msgid "Check domain failed for:"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:144
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from check: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:149
|
||||
msgid "Fetch domain expected config from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:163
|
||||
#, python-format
|
||||
msgid "Domain expected config fetched with success for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:169
|
||||
#, python-format
|
||||
msgid "Failed to fetch domain expected config for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:175
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from fetch: %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:180
|
||||
msgid "Send pending mailboxes to dimail"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:196
|
||||
#, python-format
|
||||
msgid "Failed to send the following mailboxes : %(mailboxes)s."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:202
|
||||
#, python-format
|
||||
msgid "Pending mailboxes successfully sent for %(domain)s."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/admin.py:208
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from : %(domains)s"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/apps.py:11
|
||||
msgid "Mailbox manager"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/enums.py:13
|
||||
msgid "Viewer"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/enums.py:22 mailbox_manager/enums.py:32
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/enums.py:23 mailbox_manager/enums.py:33
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/enums.py:24 mailbox_manager/enums.py:34
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/enums.py:25
|
||||
msgid "Action required"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:35
|
||||
msgid "[La Suite] Your domain is ready"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:40
|
||||
msgid "[La Suite] Your domain requires action"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:45
|
||||
msgid "[La Suite] Your domain has failed"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:71
|
||||
msgid "support email"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:75
|
||||
msgid "last check details"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:76
|
||||
msgid "A JSON object containing the last health check details"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:81
|
||||
msgid "expected config"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:82
|
||||
msgid "A JSON object containing the expected config"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:87
|
||||
msgid "Mail domain"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:88
|
||||
msgid "Mail domains"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:204
|
||||
msgid "User/mail domain relation"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:205
|
||||
msgid "User/mail domain relations"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:278
|
||||
msgid "local_part"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:292
|
||||
msgid "secondary email address"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:303
|
||||
msgid "email"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:309
|
||||
msgid "Mailbox"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:310
|
||||
msgid "Mailboxes"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:352
|
||||
msgid "You can't create or update a mailbox for a disabled domain."
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:409
|
||||
msgid "Mail domain invitation"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:410
|
||||
msgid "Mail domain invitations"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:439
|
||||
msgid "[La Suite] You have been invited to join La Régie"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:483
|
||||
msgid "destination address"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:501
|
||||
msgid "Alias"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/models.py:502
|
||||
msgid "Aliases"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/utils/dimail.py:296
|
||||
msgid "Your new mailbox information"
|
||||
msgstr ""
|
||||
|
||||
#: mailbox_manager/utils/dimail.py:307
|
||||
msgid "Your password has been updated"
|
||||
msgstr ""
|
||||
|
||||
#: people/settings.py:159
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: people/settings.py:160
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-people\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-19 20:15+0000\n"
|
||||
"PO-Revision-Date: 2026-01-19 20:18\n"
|
||||
"POT-Creation-Date: 2026-02-13 11:10+0000\n"
|
||||
"PO-Revision-Date: 2026-02-16 14:19\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -17,590 +17,336 @@ msgstr ""
|
||||
"X-Crowdin-File: backend.pot\n"
|
||||
"X-Crowdin-File-ID: 2\n"
|
||||
|
||||
#: build/lib/core/admin.py:62 core/admin.py:62
|
||||
#: core/admin.py:62
|
||||
msgid "Personal info"
|
||||
msgstr "Informations personnelles"
|
||||
|
||||
#: build/lib/core/admin.py:73 core/admin.py:73
|
||||
#: core/admin.py:73
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
|
||||
#: build/lib/core/admin.py:85 core/admin.py:85
|
||||
#: core/admin.py:85
|
||||
msgid "Important dates"
|
||||
msgstr "Dates importantes"
|
||||
|
||||
#: build/lib/core/admin.py:124 core/admin.py:124
|
||||
#: core/admin.py:124
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: build/lib/core/admin.py:226 core/admin.py:226
|
||||
#: core/admin.py:226
|
||||
msgid "Run post creation plugins"
|
||||
msgstr "Exécuter les plugins de post-création"
|
||||
|
||||
#: build/lib/core/admin.py:234 core/admin.py:234
|
||||
#: core/admin.py:234
|
||||
msgid "Post creation plugins have been run for the selected organizations."
|
||||
msgstr "Les plugins de post-création ont été exécutés pour les organisations sélectionnées."
|
||||
|
||||
#: build/lib/core/apps.py:65 core/apps.py:65
|
||||
#: core/apps.py:65
|
||||
msgid "People core application"
|
||||
msgstr "Application cœur de People"
|
||||
|
||||
#: build/lib/core/authentication/backends.py:104
|
||||
#: core/authentication/backends.py:104
|
||||
#: core/authentication/backends.py:102
|
||||
msgid "Claims contained no recognizable user identification"
|
||||
msgstr "Les claims ne contiennent aucune identification reconnaissable pour l'organisation"
|
||||
|
||||
#: build/lib/core/authentication/backends.py:124
|
||||
#: core/authentication/backends.py:124
|
||||
#: core/authentication/backends.py:122
|
||||
msgid "Claims contained no recognizable organization identification"
|
||||
msgstr "Les claims ne contiennent aucune identification reconnaissable pour l'organisation"
|
||||
|
||||
#: build/lib/core/authentication/backends.py:181
|
||||
#: build/lib/core/authentication/backends.py:183
|
||||
#: core/authentication/backends.py:181 core/authentication/backends.py:183
|
||||
#: core/authentication/backends.py:170 core/authentication/backends.py:172
|
||||
msgid "Invalid authorization header."
|
||||
msgstr "En-tête d'autorisation invalide."
|
||||
|
||||
#: build/lib/core/authentication/backends.py:188
|
||||
#: core/authentication/backends.py:188
|
||||
#: core/authentication/backends.py:177
|
||||
msgid "Invalid api key."
|
||||
msgstr "Clé API invalide."
|
||||
|
||||
#: build/lib/core/enums.py:24 core/enums.py:24
|
||||
#: core/enums.py:24
|
||||
msgid "Failure"
|
||||
msgstr "En échec"
|
||||
|
||||
#: build/lib/core/enums.py:25 build/lib/mailbox_manager/enums.py:21
|
||||
#: build/lib/mailbox_manager/enums.py:31 core/enums.py:25
|
||||
#: mailbox_manager/enums.py:21 mailbox_manager/enums.py:31
|
||||
#: core/enums.py:25 mailbox_manager/enums.py:21 mailbox_manager/enums.py:31
|
||||
msgid "Pending"
|
||||
msgstr "En attente"
|
||||
|
||||
#: build/lib/core/enums.py:26 core/enums.py:26
|
||||
#: core/enums.py:26
|
||||
msgid "Success"
|
||||
msgstr "Réussi"
|
||||
|
||||
#: build/lib/core/models.py:76 core/models.py:76
|
||||
#: core/exceptions.py:16
|
||||
msgid "Email already known. Invitation not sent but access created instead."
|
||||
msgstr "Adresse email déjà connue. Aucune invitation envoyée mais accès créé à la place."
|
||||
|
||||
#: core/models.py:77
|
||||
msgid "Member"
|
||||
msgstr "Membre"
|
||||
|
||||
#: build/lib/core/models.py:77 build/lib/core/models.py:89
|
||||
#: build/lib/mailbox_manager/enums.py:14 core/models.py:77 core/models.py:89
|
||||
#: mailbox_manager/enums.py:14
|
||||
#: core/models.py:78 core/models.py:90 mailbox_manager/enums.py:14
|
||||
msgid "Administrator"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: build/lib/core/models.py:78 build/lib/mailbox_manager/enums.py:15
|
||||
#: core/models.py:78 mailbox_manager/enums.py:15
|
||||
#: core/models.py:79 mailbox_manager/enums.py:15
|
||||
msgid "Owner"
|
||||
msgstr "Propriétaire"
|
||||
|
||||
#: build/lib/core/models.py:101 core/models.py:101
|
||||
#: core/models.py:102
|
||||
msgid "id"
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:102 core/models.py:102
|
||||
#: core/models.py:103
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "clé primaire pour l'enregistrement en tant que UUID"
|
||||
|
||||
#: build/lib/core/models.py:108 core/models.py:108
|
||||
#: core/models.py:109
|
||||
msgid "created at"
|
||||
msgstr "créé le"
|
||||
|
||||
#: build/lib/core/models.py:109 core/models.py:109
|
||||
#: core/models.py:110
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "date et heure de création de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:114 core/models.py:114
|
||||
#: core/models.py:115
|
||||
msgid "updated at"
|
||||
msgstr "mis à jour le"
|
||||
|
||||
#: build/lib/core/models.py:115 core/models.py:115
|
||||
#: core/models.py:116
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "date et heure de la dernière mise à jour de l'enregistrement"
|
||||
|
||||
#: build/lib/core/models.py:154 core/models.py:154
|
||||
#: core/models.py:155
|
||||
msgid "full name"
|
||||
msgstr "nom complet"
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
#: core/models.py:156
|
||||
msgid "short name"
|
||||
msgstr "nom court"
|
||||
|
||||
#: build/lib/core/models.py:158 core/models.py:158
|
||||
#: core/models.py:159
|
||||
msgid "notes"
|
||||
msgstr "notes"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
#: core/models.py:161
|
||||
msgid "contact information"
|
||||
msgstr "informations de contact"
|
||||
|
||||
#: build/lib/core/models.py:161 core/models.py:161
|
||||
#: core/models.py:162
|
||||
msgid "A JSON object containing the contact information"
|
||||
msgstr "Un objet JSON contenant les informations de contact"
|
||||
|
||||
#: build/lib/core/models.py:175 core/models.py:175
|
||||
#: core/models.py:176
|
||||
msgid "contact"
|
||||
msgstr "contact"
|
||||
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
#: core/models.py:177
|
||||
msgid "contacts"
|
||||
msgstr "contacts"
|
||||
|
||||
#: build/lib/core/models.py:250 build/lib/core/models.py:364
|
||||
#: build/lib/core/models.py:510 build/lib/core/models.py:1121
|
||||
#: build/lib/mailbox_manager/models.py:54 core/models.py:250 core/models.py:364
|
||||
#: core/models.py:510 core/models.py:1121 mailbox_manager/models.py:54
|
||||
#: core/models.py:251 core/models.py:365 core/models.py:511 core/models.py:1125
|
||||
#: mailbox_manager/models.py:56
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: build/lib/core/models.py:252 core/models.py:252
|
||||
#: core/models.py:253
|
||||
msgid "audience id"
|
||||
msgstr "ID d'audience"
|
||||
|
||||
#: build/lib/core/models.py:257 core/models.py:257
|
||||
#: core/models.py:258
|
||||
msgid "service provider"
|
||||
msgstr "fournisseur de services"
|
||||
|
||||
#: build/lib/core/models.py:258 core/models.py:258
|
||||
#: core/models.py:259
|
||||
msgid "service providers"
|
||||
msgstr "fournisseurs de services"
|
||||
|
||||
#: build/lib/core/models.py:372 core/models.py:372
|
||||
#: core/models.py:373
|
||||
msgid "registration ID list"
|
||||
msgstr "liste d'identifiants d'inscription"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: core/models.py:380
|
||||
msgid "domain list"
|
||||
msgstr "liste des domaines"
|
||||
|
||||
#: build/lib/core/models.py:386 core/models.py:386
|
||||
#: core/models.py:387
|
||||
msgid "metadata"
|
||||
msgstr "métadonnées"
|
||||
|
||||
#: build/lib/core/models.py:387 core/models.py:387
|
||||
#: core/models.py:388
|
||||
msgid "A JSON object containing the organization metadata"
|
||||
msgstr "Un objet JSON contenant les métadonnées de l'organisation"
|
||||
|
||||
#: build/lib/core/models.py:397 build/lib/core/models.py:535 core/models.py:397
|
||||
#: core/models.py:535
|
||||
#: core/models.py:398 core/models.py:536
|
||||
msgid "active"
|
||||
msgstr "actif"
|
||||
|
||||
#: build/lib/core/models.py:403 core/models.py:403
|
||||
#: core/models.py:404
|
||||
msgid "organization"
|
||||
msgstr "organisation"
|
||||
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
#: core/models.py:405
|
||||
msgid "organizations"
|
||||
msgstr "organisations"
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
#: core/models.py:412
|
||||
msgid "An organization must have at least a registration ID or a domain."
|
||||
msgstr "Une organisation doit avoir au moins un ID d'enregistrement ou un domaine."
|
||||
|
||||
#: build/lib/core/models.py:495 core/models.py:495
|
||||
#: core/models.py:496
|
||||
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_ characters."
|
||||
msgstr "Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des chiffres et des caractères @/./+/-/_."
|
||||
|
||||
#: build/lib/core/models.py:501 core/models.py:501
|
||||
#: core/models.py:502
|
||||
msgid "sub"
|
||||
msgstr "sub"
|
||||
|
||||
#: build/lib/core/models.py:503 core/models.py:503
|
||||
#: core/models.py:504
|
||||
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ characters only."
|
||||
msgstr "Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./+/-/_ seulement."
|
||||
|
||||
#: build/lib/core/models.py:509 build/lib/core/models.py:973 core/models.py:509
|
||||
#: core/models.py:973
|
||||
#: core/models.py:510 core/models.py:974
|
||||
msgid "email address"
|
||||
msgstr "adresse email"
|
||||
|
||||
#: build/lib/core/models.py:515 core/models.py:515
|
||||
#: core/models.py:516
|
||||
msgid "language"
|
||||
msgstr "langue"
|
||||
|
||||
#: build/lib/core/models.py:516 core/models.py:516
|
||||
#: core/models.py:517
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "La langue dans laquelle l'utilisateur veut voir l'interface."
|
||||
|
||||
#: build/lib/core/models.py:522 core/models.py:522
|
||||
#: core/models.py:523
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
|
||||
|
||||
#: build/lib/core/models.py:525 core/models.py:525
|
||||
#: core/models.py:526
|
||||
msgid "device"
|
||||
msgstr "appareil"
|
||||
|
||||
#: build/lib/core/models.py:527 core/models.py:527
|
||||
#: core/models.py:528
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
|
||||
|
||||
#: build/lib/core/models.py:530 core/models.py:530
|
||||
#: core/models.py:531
|
||||
msgid "staff status"
|
||||
msgstr "statut d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:532 core/models.py:532
|
||||
#: core/models.py:533
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
|
||||
|
||||
#: build/lib/core/models.py:538 core/models.py:538
|
||||
#: core/models.py:539
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Si cet utilisateur doit être traité comme actif. Désélectionnez ceci au lieu de supprimer des comptes."
|
||||
|
||||
#: build/lib/core/models.py:557 core/models.py:557
|
||||
#: core/models.py:558
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:558 core/models.py:558
|
||||
#: core/models.py:559
|
||||
msgid "users"
|
||||
msgstr "utilisateurs"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: core/models.py:697
|
||||
msgid "Organization/user relation"
|
||||
msgstr "Relation organisation/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:697 core/models.py:697
|
||||
#: core/models.py:698
|
||||
msgid "Organization/user relations"
|
||||
msgstr "Relations organisation/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:702 core/models.py:702
|
||||
#: core/models.py:703
|
||||
msgid "This user is already in this organization."
|
||||
msgstr "Cet utilisateur est déjà dans cette organisation."
|
||||
|
||||
#: build/lib/core/models.py:761 core/models.py:761
|
||||
#: core/models.py:762
|
||||
msgid "external_id"
|
||||
msgstr "ID externe"
|
||||
|
||||
#: build/lib/core/models.py:762 core/models.py:762
|
||||
#: core/models.py:763
|
||||
msgid "Team external UUID for synchronization with external systems"
|
||||
msgstr "UUID externe de l'équipe pour la synchronisation avec des systèmes externes"
|
||||
|
||||
#: build/lib/core/models.py:787 core/models.py:787
|
||||
#: core/models.py:788
|
||||
msgid "is visible for all SP"
|
||||
msgstr "est visible pour tous les fournisseurs de service"
|
||||
|
||||
#: build/lib/core/models.py:789 core/models.py:789
|
||||
#: core/models.py:790
|
||||
msgid "Whether this team is visible to all service providers."
|
||||
msgstr "Si cette équipe est visible pour tous les fournisseurs de services."
|
||||
|
||||
#: build/lib/core/models.py:797 core/models.py:797
|
||||
#: core/models.py:798
|
||||
msgid "Team"
|
||||
msgstr "Équipe"
|
||||
|
||||
#: build/lib/core/models.py:798 core/models.py:798
|
||||
#: core/models.py:799
|
||||
msgid "Teams"
|
||||
msgstr "Équipes"
|
||||
|
||||
#: build/lib/core/models.py:849 core/models.py:849
|
||||
#: core/models.py:850
|
||||
msgid "Team/user relation"
|
||||
msgstr "Relation équipe/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:850 core/models.py:850
|
||||
#: core/models.py:851
|
||||
msgid "Team/user relations"
|
||||
msgstr "Relations équipe/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:855 core/models.py:855
|
||||
#: core/models.py:856
|
||||
msgid "This user is already in this team."
|
||||
msgstr "Cet utilisateur est déjà dans cette équipe."
|
||||
|
||||
#: build/lib/core/models.py:941 core/models.py:941
|
||||
#: core/models.py:942
|
||||
msgid "url"
|
||||
msgstr "url"
|
||||
|
||||
#: build/lib/core/models.py:942 core/models.py:942
|
||||
#: core/models.py:943
|
||||
msgid "secret"
|
||||
msgstr "secret"
|
||||
|
||||
#: build/lib/core/models.py:956 core/models.py:956
|
||||
#: core/models.py:957
|
||||
msgid "Team webhook"
|
||||
msgstr "Webhook d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:957 core/models.py:957
|
||||
#: core/models.py:958
|
||||
msgid "Team webhooks"
|
||||
msgstr "Webhooks d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:1001 core/models.py:1001
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur enregistré."
|
||||
|
||||
#: build/lib/core/models.py:1015 core/models.py:1015
|
||||
#: core/models.py:1019
|
||||
msgid "Invitation to join La Régie!"
|
||||
msgstr "Invitation à rejoindre La Régie !"
|
||||
|
||||
#: build/lib/core/models.py:1061 core/models.py:1061
|
||||
#: core/models.py:1065
|
||||
msgid "Team invitation"
|
||||
msgstr "Invitation d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:1062 core/models.py:1062
|
||||
#: core/models.py:1066
|
||||
msgid "Team invitations"
|
||||
msgstr "Invitations d'équipe"
|
||||
|
||||
#: build/lib/core/models.py:1075 core/models.py:1075
|
||||
#: core/models.py:1079
|
||||
#, python-format
|
||||
msgid "[La Suite] You have been invited to become a %(role)s of a group"
|
||||
msgstr "[La Suite] Vous avez été invité(e) à être %(role)s d'un groupe"
|
||||
|
||||
#: build/lib/core/models.py:1123 core/models.py:1123
|
||||
#: core/models.py:1127
|
||||
msgid "api key"
|
||||
msgstr "Clé d'API"
|
||||
|
||||
#: build/lib/core/models.py:1128 core/models.py:1128
|
||||
#: core/models.py:1132
|
||||
msgid "allowed scopes"
|
||||
msgstr "périmètres autorisés"
|
||||
|
||||
#: build/lib/core/models.py:1129 core/models.py:1129
|
||||
#: core/models.py:1133
|
||||
msgid "Allowed scopes for this service"
|
||||
msgstr "Périmètres d'application autorisés pour ce service"
|
||||
|
||||
#: build/lib/core/models.py:1134 core/models.py:1134
|
||||
#: core/models.py:1138
|
||||
msgid "Account service"
|
||||
msgstr "Compte de service"
|
||||
|
||||
#: build/lib/core/models.py:1135 core/models.py:1135
|
||||
#: core/models.py:1139
|
||||
msgid "Account services"
|
||||
msgstr "Comptes de service"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:14 mailbox_manager/admin.py:14
|
||||
msgid "Import emails from dimail"
|
||||
msgstr "Importer les emails depuis dimail"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:32 build/lib/mailbox_manager/admin.py:71
|
||||
#: mailbox_manager/admin.py:32 mailbox_manager/admin.py:71
|
||||
#, python-format
|
||||
msgid "Synchronisation failed for %(domain)s with message: %(err)s"
|
||||
msgstr "La synchronisation a échoué pour %(domain)s avec le message : %(err)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:39 mailbox_manager/admin.py:39
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. Imported mailboxes: %(mailboxes)s"
|
||||
msgstr "La synchronisation a réussi pour %(domain)s. Importation des boîtes mails : %(mailboxes)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:46 build/lib/mailbox_manager/admin.py:90
|
||||
#: mailbox_manager/admin.py:46 mailbox_manager/admin.py:90
|
||||
#, python-format
|
||||
msgid "Sync require enabled domains. Excluded domains: %(domains)s"
|
||||
msgstr "La synchro nécessite des domaines activés. Les domaines exclus sont : %(domains)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:51 mailbox_manager/admin.py:51
|
||||
msgid "Import aliases from dimail"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:78 mailbox_manager/admin.py:78
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. %(imported_aliases)imported aliases: %(mailboxes)s"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:95 mailbox_manager/admin.py:95
|
||||
msgid "Check and update status from dimail"
|
||||
msgstr "Vérifier et mettre à jour le statut à partir de dimail"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:112 mailbox_manager/admin.py:112
|
||||
#, python-format
|
||||
msgid "- %(domain)s with message: %(err)s"
|
||||
msgstr "- %(domain)s avec le message : %(err)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:125 mailbox_manager/admin.py:125
|
||||
msgid "Check domains done with success."
|
||||
msgstr "Vérification des domaines effectuée avec succès."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:126 mailbox_manager/admin.py:126
|
||||
#, python-format
|
||||
msgid "Domains updated: %(domains)s"
|
||||
msgstr "Domaines mis à jour : %(domains)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:128 mailbox_manager/admin.py:128
|
||||
msgid "No domain updated."
|
||||
msgstr "Aucun domaine mis à jour."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:135 mailbox_manager/admin.py:135
|
||||
msgid "Check domain failed for:"
|
||||
msgstr "La vérification du domaine a échoué pour :"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:143 mailbox_manager/admin.py:143
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from check: %(domains)s"
|
||||
msgstr "Les domaines désactivés sont exclus de la vérification : %(domains)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:148 mailbox_manager/admin.py:148
|
||||
msgid "Fetch domain expected config from dimail"
|
||||
msgstr "Récupérer la configuration attendue du domaine depuis dimail"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:162 mailbox_manager/admin.py:162
|
||||
#, python-format
|
||||
msgid "Domain expected config fetched with success for %(domain)s."
|
||||
msgstr "La configuration du domaine attendue a été récupérée avec succès pour %(domain)s."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:168 mailbox_manager/admin.py:168
|
||||
#, python-format
|
||||
msgid "Failed to fetch domain expected config for %(domain)s."
|
||||
msgstr "Impossible de récupérer la configuration attendue pour %(domain)s."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:174 mailbox_manager/admin.py:174
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from fetch: %(domains)s"
|
||||
msgstr "Les domaines désactivés sont exclus de la récupération : %(domains)s"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:179 mailbox_manager/admin.py:179
|
||||
msgid "Send pending mailboxes to dimail"
|
||||
msgstr "Envoyer les adresses mail en attente à dimail"
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:195 mailbox_manager/admin.py:195
|
||||
#, python-format
|
||||
msgid "Failed to send the following mailboxes : %(mailboxes)s."
|
||||
msgstr "Échec de l'envoi des adresses mail suivantes : %(mailboxes)s."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:201 mailbox_manager/admin.py:201
|
||||
#, python-format
|
||||
msgid "Pending mailboxes successfully sent for %(domain)s."
|
||||
msgstr "Succès de l'envoi des adresses en attente pour le domaine %(domain)s."
|
||||
|
||||
#: build/lib/mailbox_manager/admin.py:207 mailbox_manager/admin.py:207
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from : %(domains)s"
|
||||
msgstr "Les domaines désactivés ont été exclu : %(domains)s"
|
||||
|
||||
#: build/lib/mailbox_manager/apps.py:11 mailbox_manager/apps.py:11
|
||||
msgid "Mailbox manager"
|
||||
msgstr "Gestion des boîtes mails"
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:13 mailbox_manager/enums.py:13
|
||||
msgid "Viewer"
|
||||
msgstr "Lecteur"
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:22 build/lib/mailbox_manager/enums.py:32
|
||||
#: mailbox_manager/enums.py:22 mailbox_manager/enums.py:32
|
||||
msgid "Enabled"
|
||||
msgstr "Actif"
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:23 build/lib/mailbox_manager/enums.py:33
|
||||
#: mailbox_manager/enums.py:23 mailbox_manager/enums.py:33
|
||||
msgid "Failed"
|
||||
msgstr "En erreur"
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:24 build/lib/mailbox_manager/enums.py:34
|
||||
#: mailbox_manager/enums.py:24 mailbox_manager/enums.py:34
|
||||
msgid "Disabled"
|
||||
msgstr "Désactivé"
|
||||
|
||||
#: build/lib/mailbox_manager/enums.py:25 mailbox_manager/enums.py:25
|
||||
msgid "Action required"
|
||||
msgstr "Action requise"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:33 mailbox_manager/models.py:33
|
||||
msgid "[La Suite] Your domain is ready"
|
||||
msgstr "[La Suite] Votre domaine est prêt"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:38 mailbox_manager/models.py:38
|
||||
msgid "[La Suite] Your domain requires action"
|
||||
msgstr "[La Suite] Des actions sont requises sur votre domaine"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:43 mailbox_manager/models.py:43
|
||||
msgid "[La Suite] Your domain has failed"
|
||||
msgstr "[La Suite] Votre domaine est en erreur"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:69 mailbox_manager/models.py:69
|
||||
msgid "support email"
|
||||
msgstr "adresse email du support"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:73 mailbox_manager/models.py:73
|
||||
msgid "last check details"
|
||||
msgstr "détails de la dernière vérification"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:74 mailbox_manager/models.py:74
|
||||
msgid "A JSON object containing the last health check details"
|
||||
msgstr "Un objet JSON contenant les derniers détails du bilan de santé"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:79 mailbox_manager/models.py:79
|
||||
msgid "expected config"
|
||||
msgstr "configuration attendue"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:80 mailbox_manager/models.py:80
|
||||
msgid "A JSON object containing the expected config"
|
||||
msgstr "Un objet JSON contenant la configuration attendue"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:85 mailbox_manager/models.py:85
|
||||
msgid "Mail domain"
|
||||
msgstr "Domaine de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:86 mailbox_manager/models.py:86
|
||||
msgid "Mail domains"
|
||||
msgstr "Domaines de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:202 mailbox_manager/models.py:202
|
||||
msgid "User/mail domain relation"
|
||||
msgstr "Relation utilisateur/domaine de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:203 mailbox_manager/models.py:203
|
||||
msgid "User/mail domain relations"
|
||||
msgstr "Relations utilisateur/domaine de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:276 mailbox_manager/models.py:276
|
||||
msgid "local_part"
|
||||
msgstr "local_part"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:290 mailbox_manager/models.py:290
|
||||
msgid "secondary email address"
|
||||
msgstr "adresse email secondaire"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:301 mailbox_manager/models.py:301
|
||||
msgid "email"
|
||||
msgstr "email"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:307 mailbox_manager/models.py:307
|
||||
msgid "Mailbox"
|
||||
msgstr "Boîte mail"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:308 mailbox_manager/models.py:308
|
||||
msgid "Mailboxes"
|
||||
msgstr "Boîtes mail"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:350 mailbox_manager/models.py:350
|
||||
msgid "You can't create or update a mailbox for a disabled domain."
|
||||
msgstr "Vous ne pouvez pas créer ou mettre à jour une boîte mail pour un domaine désactivé."
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:407 mailbox_manager/models.py:407
|
||||
msgid "Mail domain invitation"
|
||||
msgstr "Invitation au domaine de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:408 mailbox_manager/models.py:408
|
||||
msgid "Mail domain invitations"
|
||||
msgstr "Invitations au domaine de messagerie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:420 mailbox_manager/models.py:420
|
||||
msgid "[La Suite] You have been invited to join La Régie"
|
||||
msgstr "[La Suite] Vous avez été invité(e) à rejoindre la Régie"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:463 mailbox_manager/models.py:463
|
||||
msgid "destination address"
|
||||
msgstr "adresse de destination"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:474 mailbox_manager/models.py:474
|
||||
msgid "Alias"
|
||||
msgstr "Alias"
|
||||
|
||||
#: build/lib/mailbox_manager/models.py:475 mailbox_manager/models.py:475
|
||||
msgid "Aliases"
|
||||
msgstr "Alias"
|
||||
|
||||
#: build/lib/mailbox_manager/utils/dimail.py:296
|
||||
#: mailbox_manager/utils/dimail.py:296
|
||||
msgid "Your new mailbox information"
|
||||
msgstr "Informations sur votre nouvelle boîte mail"
|
||||
|
||||
#: build/lib/mailbox_manager/utils/dimail.py:307
|
||||
#: mailbox_manager/utils/dimail.py:307
|
||||
msgid "Your password has been updated"
|
||||
msgstr "Votre mot de passe a été mis à jour"
|
||||
|
||||
#: build/lib/people/settings.py:159 people/settings.py:159
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: build/lib/people/settings.py:160 people/settings.py:160
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: core/templates/mail/html/maildomain_action_required.html:195
|
||||
#: core/templates/mail/text/maildomain_action_required.txt:5
|
||||
msgid "Some actions are required on your domain"
|
||||
@@ -683,7 +429,7 @@ msgstr "Hourra !"
|
||||
#: core/templates/mail/html/maildomain_enabled.html:206
|
||||
#, python-format
|
||||
msgid "Your domain <b>%(name)s</b> can be used now."
|
||||
msgstr "Votre domaine <b>%(name)s</b> est prêt à être exploité."
|
||||
msgstr "Votre domaine <b>%(name)s</b> est prêt à être utilisé."
|
||||
|
||||
#: core/templates/mail/html/maildomain_enabled.html:211
|
||||
#: core/templates/mail/html/maildomain_invitation.html:211
|
||||
@@ -892,3 +638,236 @@ msgstr "Le domaine %(name)s a rencontré une erreur. Tant que cette erreur persi
|
||||
msgid "For more information: Visit the website for La Suite numérique [%(link)s] to discover what tools we offer."
|
||||
msgstr "Pour en savoir plus : Visitez le site de la Suite numérique [%(link)s] pour découvrir l’ensemble des outils proposés."
|
||||
|
||||
#: mailbox_manager/admin.py:14
|
||||
msgid "Import emails from dimail"
|
||||
msgstr "Importer les emails depuis dimail"
|
||||
|
||||
#: mailbox_manager/admin.py:32 mailbox_manager/admin.py:71
|
||||
#, python-format
|
||||
msgid "Synchronisation failed for %(domain)s with message: %(err)s"
|
||||
msgstr "La synchronisation a échoué pour %(domain)s avec le message : %(err)s"
|
||||
|
||||
#: mailbox_manager/admin.py:39
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s. Imported mailboxes: %(mailboxes)s"
|
||||
msgstr "La synchronisation a réussi pour %(domain)s. Importation des boîtes mails : %(mailboxes)s"
|
||||
|
||||
#: mailbox_manager/admin.py:46 mailbox_manager/admin.py:91
|
||||
#, python-format
|
||||
msgid "Sync require enabled domains. Excluded domains: %(domains)s"
|
||||
msgstr "La synchro nécessite des domaines activés. Les domaines exclus sont : %(domains)s"
|
||||
|
||||
#: mailbox_manager/admin.py:51
|
||||
msgid "Import aliases from dimail"
|
||||
msgstr "Importer des alias depuis dimail"
|
||||
|
||||
#: mailbox_manager/admin.py:78
|
||||
#, python-format
|
||||
msgid "Synchronisation succeed for %(domain)s.Imported %(count_imported)s aliases: %(aliases)s"
|
||||
msgstr "Synchronisation réussie pour %(domain)s. %(count_imported)s alias importés : %(aliases)s"
|
||||
|
||||
#: mailbox_manager/admin.py:96
|
||||
msgid "Check and update status from dimail"
|
||||
msgstr "Vérifier et mettre à jour le statut à partir de dimail"
|
||||
|
||||
#: mailbox_manager/admin.py:113
|
||||
#, python-format
|
||||
msgid "- %(domain)s with message: %(err)s"
|
||||
msgstr "- %(domain)s avec le message : %(err)s"
|
||||
|
||||
#: mailbox_manager/admin.py:126
|
||||
msgid "Check domains done with success."
|
||||
msgstr "Vérification des domaines effectuée avec succès."
|
||||
|
||||
#: mailbox_manager/admin.py:127
|
||||
#, python-format
|
||||
msgid "Domains updated: %(domains)s"
|
||||
msgstr "Domaines mis à jour : %(domains)s"
|
||||
|
||||
#: mailbox_manager/admin.py:129
|
||||
msgid "No domain updated."
|
||||
msgstr "Aucun domaine mis à jour."
|
||||
|
||||
#: mailbox_manager/admin.py:136
|
||||
msgid "Check domain failed for:"
|
||||
msgstr "La vérification du domaine a échoué pour :"
|
||||
|
||||
#: mailbox_manager/admin.py:144
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from check: %(domains)s"
|
||||
msgstr "Les domaines désactivés sont exclus de la vérification : %(domains)s"
|
||||
|
||||
#: mailbox_manager/admin.py:149
|
||||
msgid "Fetch domain expected config from dimail"
|
||||
msgstr "Récupérer la configuration attendue du domaine depuis dimail"
|
||||
|
||||
#: mailbox_manager/admin.py:163
|
||||
#, python-format
|
||||
msgid "Domain expected config fetched with success for %(domain)s."
|
||||
msgstr "La configuration du domaine attendue a été récupérée avec succès pour %(domain)s."
|
||||
|
||||
#: mailbox_manager/admin.py:169
|
||||
#, python-format
|
||||
msgid "Failed to fetch domain expected config for %(domain)s."
|
||||
msgstr "Impossible de récupérer la configuration attendue pour %(domain)s."
|
||||
|
||||
#: mailbox_manager/admin.py:175
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from fetch: %(domains)s"
|
||||
msgstr "Les domaines désactivés sont exclus de la récupération : %(domains)s"
|
||||
|
||||
#: mailbox_manager/admin.py:180
|
||||
msgid "Send pending mailboxes to dimail"
|
||||
msgstr "Envoyer les adresses mail en attente à dimail"
|
||||
|
||||
#: mailbox_manager/admin.py:196
|
||||
#, python-format
|
||||
msgid "Failed to send the following mailboxes : %(mailboxes)s."
|
||||
msgstr "Échec de l'envoi des adresses mail suivantes : %(mailboxes)s."
|
||||
|
||||
#: mailbox_manager/admin.py:202
|
||||
#, python-format
|
||||
msgid "Pending mailboxes successfully sent for %(domain)s."
|
||||
msgstr "Succès de l'envoi des adresses en attente pour le domaine %(domain)s."
|
||||
|
||||
#: mailbox_manager/admin.py:208
|
||||
#, python-format
|
||||
msgid "Domains disabled are excluded from : %(domains)s"
|
||||
msgstr "Les domaines désactivés ont été exclu : %(domains)s"
|
||||
|
||||
#: mailbox_manager/apps.py:11
|
||||
msgid "Mailbox manager"
|
||||
msgstr "Gestion des boîtes mails"
|
||||
|
||||
#: mailbox_manager/enums.py:13
|
||||
msgid "Viewer"
|
||||
msgstr "Lecteur"
|
||||
|
||||
#: mailbox_manager/enums.py:22 mailbox_manager/enums.py:32
|
||||
msgid "Enabled"
|
||||
msgstr "Actif"
|
||||
|
||||
#: mailbox_manager/enums.py:23 mailbox_manager/enums.py:33
|
||||
msgid "Failed"
|
||||
msgstr "En erreur"
|
||||
|
||||
#: mailbox_manager/enums.py:24 mailbox_manager/enums.py:34
|
||||
msgid "Disabled"
|
||||
msgstr "Désactivé"
|
||||
|
||||
#: mailbox_manager/enums.py:25
|
||||
msgid "Action required"
|
||||
msgstr "Action requise"
|
||||
|
||||
#: mailbox_manager/models.py:35
|
||||
msgid "[La Suite] Your domain is ready"
|
||||
msgstr "[La Suite] Votre domaine est prêt"
|
||||
|
||||
#: mailbox_manager/models.py:40
|
||||
msgid "[La Suite] Your domain requires action"
|
||||
msgstr "[La Suite] Des actions sont requises sur votre domaine"
|
||||
|
||||
#: mailbox_manager/models.py:45
|
||||
msgid "[La Suite] Your domain has failed"
|
||||
msgstr "[La Suite] Votre domaine est en erreur"
|
||||
|
||||
#: mailbox_manager/models.py:71
|
||||
msgid "support email"
|
||||
msgstr "adresse email du support"
|
||||
|
||||
#: mailbox_manager/models.py:75
|
||||
msgid "last check details"
|
||||
msgstr "détails de la dernière vérification"
|
||||
|
||||
#: mailbox_manager/models.py:76
|
||||
msgid "A JSON object containing the last health check details"
|
||||
msgstr "Un objet JSON contenant les derniers détails du bilan de santé"
|
||||
|
||||
#: mailbox_manager/models.py:81
|
||||
msgid "expected config"
|
||||
msgstr "configuration attendue"
|
||||
|
||||
#: mailbox_manager/models.py:82
|
||||
msgid "A JSON object containing the expected config"
|
||||
msgstr "Un objet JSON contenant la configuration attendue"
|
||||
|
||||
#: mailbox_manager/models.py:87
|
||||
msgid "Mail domain"
|
||||
msgstr "Domaine de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:88
|
||||
msgid "Mail domains"
|
||||
msgstr "Domaines de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:204
|
||||
msgid "User/mail domain relation"
|
||||
msgstr "Relation utilisateur/domaine de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:205
|
||||
msgid "User/mail domain relations"
|
||||
msgstr "Relations utilisateur/domaine de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:278
|
||||
msgid "local_part"
|
||||
msgstr "local_part"
|
||||
|
||||
#: mailbox_manager/models.py:292
|
||||
msgid "secondary email address"
|
||||
msgstr "adresse email secondaire"
|
||||
|
||||
#: mailbox_manager/models.py:303
|
||||
msgid "email"
|
||||
msgstr "email"
|
||||
|
||||
#: mailbox_manager/models.py:309
|
||||
msgid "Mailbox"
|
||||
msgstr "Boîte mail"
|
||||
|
||||
#: mailbox_manager/models.py:310
|
||||
msgid "Mailboxes"
|
||||
msgstr "Boîtes mail"
|
||||
|
||||
#: mailbox_manager/models.py:352
|
||||
msgid "You can't create or update a mailbox for a disabled domain."
|
||||
msgstr "Vous ne pouvez pas créer ou mettre à jour une boîte mail pour un domaine désactivé."
|
||||
|
||||
#: mailbox_manager/models.py:409
|
||||
msgid "Mail domain invitation"
|
||||
msgstr "Invitation au domaine de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:410
|
||||
msgid "Mail domain invitations"
|
||||
msgstr "Invitations au domaine de messagerie"
|
||||
|
||||
#: mailbox_manager/models.py:439
|
||||
msgid "[La Suite] You have been invited to join La Régie"
|
||||
msgstr "[La Suite] Vous avez été invité(e) à rejoindre la Régie"
|
||||
|
||||
#: mailbox_manager/models.py:483
|
||||
msgid "destination address"
|
||||
msgstr "adresse de destination"
|
||||
|
||||
#: mailbox_manager/models.py:501
|
||||
msgid "Alias"
|
||||
msgstr "Alias"
|
||||
|
||||
#: mailbox_manager/models.py:502
|
||||
msgid "Aliases"
|
||||
msgstr "Alias"
|
||||
|
||||
#: mailbox_manager/utils/dimail.py:296
|
||||
msgid "Your new mailbox information"
|
||||
msgstr "Informations sur votre nouvelle boîte mail"
|
||||
|
||||
#: mailbox_manager/utils/dimail.py:307
|
||||
msgid "Your password has been updated"
|
||||
msgstr "Votre mot de passe a été mis à jour"
|
||||
|
||||
#: people/settings.py:159
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: people/settings.py:160
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
|
||||
@@ -75,15 +75,16 @@ def sync_aliases_from_dimail(modeladmin, request, queryset): # pylint: disable=
|
||||
messages.success(
|
||||
request,
|
||||
_(
|
||||
"Synchronisation succeed for %(domain)s. %(imported_aliases)\
|
||||
imported aliases: %(mailboxes)s"
|
||||
"Synchronisation succeed for %(domain)s.\
|
||||
Imported %(count_imported)s aliases: %(aliases)s"
|
||||
)
|
||||
% {
|
||||
"domain": domain.name,
|
||||
"number_imported": len(imported_aliases),
|
||||
"mailboxes": ", ".join(imported_aliases),
|
||||
"count_imported": len(imported_aliases),
|
||||
"aliases": ", ".join(imported_aliases),
|
||||
},
|
||||
)
|
||||
|
||||
if excluded_domains:
|
||||
messages.warning(
|
||||
request,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from django.db.models import Q, Subquery
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from rest_framework import exceptions, filters, mixins, status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
@@ -9,6 +10,7 @@ from rest_framework.response import Response
|
||||
|
||||
from core import models as core_models
|
||||
from core.api.client.serializers import UserSerializer
|
||||
from core.exceptions import EmailAlreadyKnownException
|
||||
|
||||
from mailbox_manager import enums, models
|
||||
from mailbox_manager.api import permissions
|
||||
@@ -348,6 +350,7 @@ class MailDomainInvitationViewset(
|
||||
mixins.CreateModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
):
|
||||
"""API ViewSet for user invitations to domain management.
|
||||
@@ -362,9 +365,13 @@ class MailDomainInvitationViewset(
|
||||
- issuer : User, automatically added from user making query, if allowed
|
||||
- domain : Domain, automatically added from requested URI
|
||||
Return a newly created invitation
|
||||
or an access if email is already linked to an existing user
|
||||
|
||||
PUT / PATCH : Not permitted. Instead of updating your invitation,
|
||||
delete and create a new one.
|
||||
|
||||
DELETE /api/<version>/mail-domains/<domain_slug>/invitations/:<invitation_id>/
|
||||
Delete targeted invitation
|
||||
"""
|
||||
|
||||
lookup_field = "id"
|
||||
@@ -406,6 +413,30 @@ class MailDomainInvitationViewset(
|
||||
|
||||
return queryset
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Attempt to create invitation. If user is already registered,
|
||||
they don't need an invitation but an access, which we create here."""
|
||||
email = request.data["email"]
|
||||
try:
|
||||
return super().create(request, *args, **kwargs)
|
||||
except EmailAlreadyKnownException as exc:
|
||||
user = models.User.objects.get(email__iexact=email)
|
||||
|
||||
models.MailDomainAccess.objects.create(
|
||||
user=user,
|
||||
domain=models.MailDomain.objects.get(slug=kwargs["domain_slug"]),
|
||||
role=request.data["role"],
|
||||
)
|
||||
raise exc
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def refresh(self, request, *args, **kwargs): # pylint: disable=unused-argument
|
||||
"""Enable mailbox. Send a request to dimail and change status in our DB"""
|
||||
invitation = get_object_or_404(models.MailDomainInvitation, id=kwargs["id"])
|
||||
invitation.refresh()
|
||||
invitation.email_invitation()
|
||||
return Response(serializers.MailDomainInvitationSerializer(invitation).data)
|
||||
|
||||
|
||||
class AliasViewSet(
|
||||
mixins.CreateModelMixin,
|
||||
|
||||
@@ -26,7 +26,7 @@ class DomainPermission(IsAuthenticated):
|
||||
slug=view.kwargs.get("domain_slug", ""),
|
||||
accesses__user=request.user,
|
||||
)
|
||||
# domain = models.MailDomain.objects.get(slug=view.kwargs.get("domain_slug", ""))
|
||||
|
||||
abilities = domain.get_abilities(request.user)
|
||||
if request.method.lower() == "delete":
|
||||
return abilities.get("manage_accesses", False)
|
||||
|
||||
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created at')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated at')),
|
||||
('local_part', models.CharField(max_length=100)),
|
||||
('destination', models.EmailField(max_length=254, verbose_name='destination address')),
|
||||
('destination', models.CharField(max_length=254, validators=[django.core.validators.EmailValidator(allowlist=['localhost', 'devnull'])], verbose_name='destination address')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='mailbox_manager.maildomain')),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core import exceptions, mail, validators
|
||||
from django.core.validators import EmailValidator
|
||||
from django.db import models
|
||||
from django.db.models.functions import Lower
|
||||
from django.template.loader import render_to_string
|
||||
@@ -16,6 +17,7 @@ from django.utils.text import slugify
|
||||
from django.utils.translation import get_language, gettext, override
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.exceptions import EmailAlreadyKnownException
|
||||
from core.models import BaseInvitation, BaseModel, Organization, User
|
||||
|
||||
from mailbox_manager.enums import (
|
||||
@@ -109,7 +111,7 @@ class MailDomain(BaseModel):
|
||||
if user.is_authenticated:
|
||||
try:
|
||||
role = self.accesses.filter(user=user).values("role")[0]["role"]
|
||||
except (MailDomainAccess.DoesNotExist, IndexError):
|
||||
except MailDomainAccess.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
is_owner_or_admin = role in [
|
||||
@@ -222,7 +224,7 @@ class MailDomainAccess(BaseModel):
|
||||
authenticated_user_role = user.mail_domain_accesses.get(
|
||||
domain=self.domain
|
||||
).role
|
||||
except (MailDomainAccess.DoesNotExist, IndexError):
|
||||
except MailDomainAccess.DoesNotExist, IndexError:
|
||||
return []
|
||||
|
||||
# only an owner can set an owner role
|
||||
@@ -250,7 +252,7 @@ class MailDomainAccess(BaseModel):
|
||||
if user.is_authenticated:
|
||||
try:
|
||||
role = user.mail_domain_accesses.filter(domain=self.domain).get().role
|
||||
except (MailDomainAccess.DoesNotExist, IndexError):
|
||||
except MailDomainAccess.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
is_owner_or_admin = role in [
|
||||
@@ -349,6 +351,8 @@ Last name and Domain already exists.",
|
||||
raise exceptions.ValidationError(
|
||||
_("You can't create or update a mailbox for a disabled domain.")
|
||||
)
|
||||
|
||||
self.local_part = self.local_part.lower()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
@@ -412,6 +416,23 @@ class MailDomainInvitation(BaseInvitation):
|
||||
)
|
||||
]
|
||||
|
||||
def refresh(self):
|
||||
"""Create domain access if refresh fail because user was created
|
||||
after invitation expiration"""
|
||||
try:
|
||||
super().refresh()
|
||||
except EmailAlreadyKnownException as exc:
|
||||
user = User.objects.get(email__iexact=self.email)
|
||||
|
||||
MailDomainAccess.objects.create(
|
||||
user=user,
|
||||
domain=self.domain,
|
||||
role=self.role,
|
||||
)
|
||||
|
||||
self.delete() # delete related invitation
|
||||
raise exc
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.email} invited to {self.domain}"
|
||||
|
||||
@@ -440,7 +461,7 @@ class MailDomainInvitation(BaseInvitation):
|
||||
role = self.domain.accesses.filter(user=user).values("role")[0][
|
||||
"role"
|
||||
]
|
||||
except (self._meta.model.DoesNotExist, IndexError):
|
||||
except self._meta.model.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
can_delete = role in [
|
||||
@@ -460,7 +481,15 @@ class Alias(BaseModel):
|
||||
"""Model for aliases."""
|
||||
|
||||
local_part = models.CharField(max_length=100, blank=False)
|
||||
destination = models.EmailField(_("destination address"), null=False, blank=False)
|
||||
destination = models.CharField(
|
||||
_("destination address"),
|
||||
max_length=254,
|
||||
null=False,
|
||||
blank=False,
|
||||
validators=[
|
||||
EmailValidator(allowlist=["localhost", "devnull"]),
|
||||
],
|
||||
)
|
||||
domain = models.ForeignKey(
|
||||
MailDomain,
|
||||
on_delete=models.CASCADE,
|
||||
@@ -488,7 +517,7 @@ class Alias(BaseModel):
|
||||
edit aliases, but also viewer if the alias points to their email."""
|
||||
try:
|
||||
role = user.mail_domain_accesses.get(domain=self.domain).role
|
||||
except (MailDomainAccess.DoesNotExist, IndexError):
|
||||
except MailDomainAccess.DoesNotExist, IndexError:
|
||||
role = None
|
||||
|
||||
is_owner_or_admin = role in [
|
||||
|
||||
@@ -182,3 +182,33 @@ def test_api_aliases_create__existing_mailbox_ok(dimail_token_ok):
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert models.Alias.objects.exists()
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_aliases_create__devnull_destination_ok(dimail_token_ok):
|
||||
"""Can create alias where destination is devnull@devnull."""
|
||||
access = factories.MailDomainAccessFactory(
|
||||
role="owner", domain=factories.MailDomainEnabledFactory()
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
|
||||
responses.post(
|
||||
re.compile(rf".*/domains/{access.domain.name}/aliases/"),
|
||||
json={
|
||||
"username": "spammy-address",
|
||||
"domain": access.domain.name,
|
||||
"destination": "devnull@devnull",
|
||||
"allow_to_send": False,
|
||||
},
|
||||
status=status.HTTP_201_CREATED,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/aliases/",
|
||||
{"local_part": "spammy-address", "destination": "devnull@devnull"},
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert models.Alias.objects.exists()
|
||||
|
||||
+74
-43
@@ -11,8 +11,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import enums, factories
|
||||
from mailbox_manager.api.client import serializers
|
||||
from mailbox_manager import enums, factories, models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -20,13 +19,12 @@ pytestmark = pytest.mark.django_db
|
||||
def test_api_domain_invitations__create__anonymous():
|
||||
"""Anonymous users should not be able to create invitations."""
|
||||
domain = factories.MailDomainEnabledFactory()
|
||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build()
|
||||
).data
|
||||
|
||||
response = APIClient().post(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
|
||||
invitation_values,
|
||||
{
|
||||
"email": "some.email@domain.com",
|
||||
"role": "admin",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
@@ -40,16 +38,15 @@ def test_api_domain_invitations__create__no_access_forbidden_not_found():
|
||||
for a domain they don't manage."""
|
||||
user = core_factories.UserFactory()
|
||||
domain = factories.MailDomainEnabledFactory()
|
||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build()
|
||||
).data
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
|
||||
invitation_values,
|
||||
{
|
||||
"email": "some.email@domain.com",
|
||||
"role": "viewer",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
@@ -65,24 +62,21 @@ def test_api_domain_invitations__admin_should_create_invites(role):
|
||||
domain = factories.MailDomainEnabledFactory()
|
||||
factories.MailDomainAccessFactory(domain=domain, user=user, role=role)
|
||||
|
||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build()
|
||||
).data
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
assert len(mail.outbox) == 0
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
|
||||
invitation_values,
|
||||
{
|
||||
"email": "some.email@domain.com",
|
||||
"role": "owner",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert len(mail.outbox) == 1
|
||||
email = mail.outbox[0]
|
||||
assert email.to == [invitation_values["email"]]
|
||||
assert email.to == ["some.email@domain.com"]
|
||||
assert email.subject == "[La Suite] Vous avez été invité(e) à rejoindre la Régie"
|
||||
|
||||
|
||||
@@ -96,16 +90,14 @@ def test_api_domain_invitations__no_access_forbidden_not_found():
|
||||
domain=domain, user=user, role=enums.MailDomainRoleChoices.VIEWER
|
||||
)
|
||||
|
||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build()
|
||||
).data
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
|
||||
invitation_values,
|
||||
{
|
||||
"email": "some.email@domain.com",
|
||||
"role": "viewer",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
@@ -125,42 +117,81 @@ def test_api_domain_invitations__should_not_create_duplicate_invitations():
|
||||
domain=domain, user=user, role=enums.MailDomainRoleChoices.OWNER
|
||||
)
|
||||
|
||||
# Create a new invitation to the same domain with the exact same email address
|
||||
duplicated_invitation = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build(email=existing_invitation.email)
|
||||
).data
|
||||
|
||||
# New invitation to the same domain with the exact same email address
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/",
|
||||
duplicated_invitation,
|
||||
{
|
||||
"email": existing_invitation.email,
|
||||
"role": "owner",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["__all__"] == [
|
||||
"Mail domain invitation with this Email address and Domain already exists."
|
||||
]
|
||||
assert models.MailDomainInvitation.objects.count() == 1 # and specifically, not 2
|
||||
|
||||
|
||||
def test_api_domain_invitations__should_not_invite_when_user_already_exists():
|
||||
def test_api_domain_invitations__inviting_known_email_should_create_access():
|
||||
"""Already existing users should not be invited but given access directly."""
|
||||
existing_user = core_factories.UserFactory()
|
||||
|
||||
# Grant privileged role on the domain to the user
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
||||
factories.MailDomainInvitationFactory.build(email=existing_user.email)
|
||||
).data
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
|
||||
invitation_values,
|
||||
{
|
||||
"email": existing_user.email,
|
||||
"role": "owner",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["email"] == [
|
||||
"This email is already associated to a registered user."
|
||||
]
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert (
|
||||
response.json()["detail"]
|
||||
== "Email already known. Invitation not sent but access created instead."
|
||||
)
|
||||
|
||||
assert not models.MailDomainInvitation.objects.exists()
|
||||
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
|
||||
|
||||
|
||||
def test_api_domain_invitations__inviting_known_email_case_insensitive():
|
||||
"""Email matching should be case-insensitive when creating access for existing users."""
|
||||
# Create a user with lowercase email
|
||||
existing_user = core_factories.UserFactory(email="john.doe@example.com")
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
|
||||
# Try to invite with same email different case - should create access for existing user
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
|
||||
{
|
||||
"email": "John.Doe@Example.COM",
|
||||
"role": "owner",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert (
|
||||
response.json()["detail"]
|
||||
== "Email already known. Invitation not sent but access created instead."
|
||||
)
|
||||
|
||||
# No invitation should be created
|
||||
assert not models.MailDomainInvitation.objects.exists()
|
||||
|
||||
# Access should be created for the existing user (not a new user)
|
||||
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()
|
||||
assert models.MailDomainAccess.objects.filter(
|
||||
user=existing_user, domain=access.domain
|
||||
).exists()
|
||||
|
||||
# Ensure only one user exists (no duplicate user created)
|
||||
assert models.User.objects.filter(email__iexact="john.doe@example.com").count() == 1
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Tests for MailDomainInvitation API endpoint in People's app mailbox_manager.
|
||||
Focus on "delete" action.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from mailbox_manager import factories, models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_api_domain_invitations__delete__anonymous():
|
||||
"""Anonymous users should not be able to delete invitations."""
|
||||
domain = factories.MailDomainEnabledFactory()
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
|
||||
response = APIClient().delete(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/{invitation.id}/",
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
assert models.MailDomainInvitation.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_domain_invitations__delete__no_access_not_found():
|
||||
"""Users should not be permitted to delete invitations
|
||||
on domains they don't manage."""
|
||||
domain = factories.MailDomainEnabledFactory()
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
|
||||
other_access = factories.MailDomainAccessFactory(role="owner") # unrelated access
|
||||
client = APIClient()
|
||||
client.force_login(other_access.user)
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1.0/mail-domains/{domain.slug}/invitations/{invitation.id}/",
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
assert models.MailDomainInvitation.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_domain_invitations__delete__viewers_forbidden():
|
||||
"""Domain viewers should not be permitted to delete invitations."""
|
||||
access = factories.MailDomainAccessFactory(role="viewer")
|
||||
invitation = factories.MailDomainInvitationFactory(domain=access.domain)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
response = client.delete(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/{invitation.id}/",
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
assert models.MailDomainInvitation.objects.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"role",
|
||||
["owner", "administrator"],
|
||||
)
|
||||
def test_api_domain_invitations__delete_admins_ok(role):
|
||||
"""Domain owners and admins should be able to delete invitations."""
|
||||
access = factories.MailDomainAccessFactory(role=role)
|
||||
invitation = factories.MailDomainInvitationFactory(domain=access.domain)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
response = client.delete(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/{invitation.id}/",
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
assert not models.MailDomainInvitation.objects.exists()
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Tests for MailDomainInvitations API endpoint in People's app mailbox_manager.
|
||||
Focus on "refresh" action.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import enums, factories, models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_api_domain_invitations__anonymous_cannot_refresh():
|
||||
"""Anonymous are not allowed to refresh."""
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
response = APIClient().post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_api_domain_invitations__no_access_cannot_see_invitation():
|
||||
"""Users with no permission on the domain should be returned a 404 not found."""
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
|
||||
otro_access = factories.MailDomainAccessFactory(
|
||||
role=enums.MailDomainRoleChoices.ADMIN
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(otro_access.user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
def test_api_domain_invitations__viewer_cannot_refresh():
|
||||
"""Viewers cannot refresh invitations."""
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
access = factories.MailDomainAccessFactory(
|
||||
domain=invitation.domain, role=enums.MailDomainRoleChoices.VIEWER
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
|
||||
|
||||
def test_api_domain_invitations__admins_can_refresh_expired_invitations():
|
||||
"""Admins can refresh invitations."""
|
||||
with freeze_time("2025-12-16"):
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
|
||||
access = factories.MailDomainAccessFactory(
|
||||
domain=invitation.domain, role=enums.MailDomainRoleChoices.OWNER
|
||||
)
|
||||
|
||||
assert invitation.is_expired is True # check invitation is correctly expired
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
# can refresh expired invitations
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["is_expired"] is False
|
||||
assert models.MailDomainInvitation.objects.count() == 1
|
||||
|
||||
invitation.refresh_from_db()
|
||||
|
||||
# can also refresh valid
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["is_expired"] is False
|
||||
assert models.MailDomainInvitation.objects.count() == 1
|
||||
|
||||
|
||||
def test_api_domain_invitations__refreshing_invitation_can_convert_to_access():
|
||||
"""In the event of someone creating their account after their invitation expired,
|
||||
said invitation has not been converted to access.
|
||||
We don't want to create a new invitation but create an access instead."""
|
||||
with freeze_time("2025-12-16"):
|
||||
invitation = factories.MailDomainInvitationFactory()
|
||||
core_factories.UserFactory(email=invitation.email)
|
||||
|
||||
access = factories.MailDomainAccessFactory(
|
||||
domain=invitation.domain, role=enums.MailDomainRoleChoices.OWNER
|
||||
)
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/{invitation.id}/refresh/"
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert (
|
||||
response.json()["detail"]
|
||||
== "Email already known. Invitation not sent but access created instead."
|
||||
)
|
||||
assert models.MailDomainAccess.objects.count() == 2
|
||||
assert not models.MailDomainInvitation.objects.exists()
|
||||
@@ -58,7 +58,7 @@ def test_api_mailboxes__create_no_access_forbidden_not_found(mailbox_data):
|
||||
assert not models.Mailbox.objects.exists()
|
||||
|
||||
|
||||
def test_api_mailboxes__create_viewer_failure(mailbox_data):
|
||||
def test_api_mailboxes__create_viewer_forbidden(mailbox_data):
|
||||
"""Users with viewer role should not be able to create mailbox on the mail domain."""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
access = factories.MailDomainAccessFactory(
|
||||
@@ -89,7 +89,7 @@ def test_api_mailboxes__create_display_name_must_be_unique():
|
||||
|
||||
new_mailbox_data = {
|
||||
"local_part": "something_else",
|
||||
"first_name": existing_mailbox.first_name.upper(), # ensure case-insensitivity
|
||||
"first_name": existing_mailbox.first_name.upper(), # checks case-insensitivity
|
||||
"last_name": existing_mailbox.last_name.lower(),
|
||||
}
|
||||
response = client.post(
|
||||
@@ -239,6 +239,35 @@ def test_api_mailboxes__create_with_accent_success(role, dimail_token_ok):
|
||||
}
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_mailboxes__create_lowercase(dimail_token_ok, mailbox_data):
|
||||
"""Should lowercase emails on creation."""
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.ADMIN)
|
||||
mailbox_data["local_part"] = "WeiRdCaSe"
|
||||
|
||||
# ensure response
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
re.compile(rf".*/domains/{access.domain.name}/mailboxes/"),
|
||||
body=response_mailbox_created(
|
||||
f"{mailbox_data['local_part']}@{access.domain.name}"
|
||||
),
|
||||
status=status.HTTP_201_CREATED,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{access.domain.slug}/mailboxes/",
|
||||
mailbox_data,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.json()["local_part"] == mailbox_data["local_part"].lower()
|
||||
|
||||
|
||||
def test_api_mailboxes__create_administrator_missing_fields():
|
||||
"""
|
||||
Administrator users should not be able to create mailboxes
|
||||
|
||||
+4
-14
@@ -113,7 +113,7 @@ Please add a valid secondary email before trying again."
|
||||
],
|
||||
)
|
||||
@responses.activate
|
||||
def test_api_mailboxes__reset_password_admin_successful(role):
|
||||
def test_api_mailboxes__reset_password_admin_successful(role, dimail_token_ok): # pylint: disable=W0613
|
||||
"""Owner and admin users should be able to reset password on mailboxes.
|
||||
New password should be sent to secondary email."""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
@@ -124,12 +124,7 @@ def test_api_mailboxes__reset_password_admin_successful(role):
|
||||
client.force_login(access.user)
|
||||
dimail_url = settings.MAIL_PROVISIONING_API_URL
|
||||
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"{dimail_url}/token/",
|
||||
body=dimail.TOKEN_OK,
|
||||
status=200,
|
||||
)
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f"{dimail_url}/domains/{mail_domain.name}/mailboxes/{mailbox.local_part}/reset_password/",
|
||||
@@ -161,7 +156,7 @@ def test_api_mailboxes__reset_password_non_existing():
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_mailboxes__reset_password_connexion_failed():
|
||||
def test_api_mailboxes__reset_password_connexion_failed(dimail_token_ok): # pylint: disable=W0613
|
||||
"""
|
||||
No mail is sent when password reset failed because of connexion error.
|
||||
"""
|
||||
@@ -175,12 +170,7 @@ def test_api_mailboxes__reset_password_connexion_failed():
|
||||
client.force_login(access.user)
|
||||
|
||||
dimail_url = settings.MAIL_PROVISIONING_API_URL
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"{dimail_url}/token/",
|
||||
body=dimail.TOKEN_OK,
|
||||
status=200,
|
||||
)
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
f"{dimail_url}/domains/{mail_domain.name}/mailboxes/{mailbox.local_part}/reset_password/",
|
||||
|
||||
@@ -9,17 +9,15 @@ import responses
|
||||
from rest_framework import status
|
||||
|
||||
from mailbox_manager import factories
|
||||
from mailbox_manager.tests.fixtures.dimail import TOKEN_OK
|
||||
|
||||
|
||||
## DIMAIL RESPONSES
|
||||
@pytest.fixture(name="dimail_token_ok")
|
||||
def fixture_dimail_token_ok():
|
||||
"""Mock dimail response when /token/ endpoit is given valid credentials."""
|
||||
responses.add(
|
||||
responses.GET,
|
||||
responses.get(
|
||||
re.compile(r".*/token/"),
|
||||
body=TOKEN_OK,
|
||||
json={"access_token": "token", "token_type": "bearer"},
|
||||
status=status.HTTP_200_OK,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
+1
-5
@@ -287,10 +287,6 @@ DOMAIN_SPEC = [
|
||||
]
|
||||
|
||||
|
||||
## TOKEN
|
||||
TOKEN_OK = json.dumps({"access_token": "token", "token_type": "bearer"})
|
||||
|
||||
|
||||
## ALLOWS
|
||||
def response_allows_created(user_name, domain_name):
|
||||
"""mimic dimail response upon successful allows creation.
|
||||
@@ -301,4 +297,4 @@ def response_allows_created(user_name, domain_name):
|
||||
## MAILBOXES
|
||||
def response_mailbox_created(email_address):
|
||||
"""mimic dimail response upon successful mailbox creation."""
|
||||
return json.dumps({"email": email_address, "password": "password"})
|
||||
return json.dumps({"email": email_address.lower(), "password": "password"})
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Unit tests for the Alias model
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from mailbox_manager import factories, models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_models_aliases__devnull_destination_ok():
|
||||
"""Can create alias where destination is devnull@devnull."""
|
||||
|
||||
models.Alias.objects.create(
|
||||
local_part="spam",
|
||||
domain=factories.MailDomainEnabledFactory(),
|
||||
destination="devnull@devnull",
|
||||
)
|
||||
@@ -19,7 +19,6 @@ from .fixtures.dimail import (
|
||||
CHECK_DOMAIN_BROKEN,
|
||||
CHECK_DOMAIN_OK,
|
||||
DOMAIN_SPEC,
|
||||
TOKEN_OK,
|
||||
response_mailbox_created,
|
||||
)
|
||||
|
||||
@@ -101,7 +100,9 @@ def test_fetch_domain_status__should_switch_to_failed_when_domain_broken(client)
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.django_db
|
||||
def test_fetch_domain_status__should_switch_to_enabled_when_domain_ok(client):
|
||||
def test_fetch_domain_status__should_switch_to_enabled_when_domain_ok(
|
||||
client, dimail_token_ok
|
||||
): # pylint: disable=W0613
|
||||
"""Test admin action should switch domain state to ENABLED
|
||||
when dimail's response is "ok". It should also activate any pending mailbox."""
|
||||
admin = core_factories.UserFactory(is_staff=True, is_superuser=True)
|
||||
@@ -127,13 +128,7 @@ def test_fetch_domain_status__should_switch_to_enabled_when_domain_ok(client):
|
||||
content_type="application/json",
|
||||
)
|
||||
# we need to get a token to create mailboxes
|
||||
responses.add(
|
||||
responses.GET,
|
||||
re.compile(r".*/token/"),
|
||||
body=TOKEN_OK,
|
||||
status=status.HTTP_200_OK,
|
||||
content_type="application/json",
|
||||
)
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
re.compile(rf".*/domains/{domain1.name}/mailboxes/"),
|
||||
@@ -208,7 +203,7 @@ def test_fetch_domain_expected_config__should_not_fetch_for_disabled_domain(clie
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.django_db
|
||||
def test_send_pending_mailboxes(client):
|
||||
def test_send_pending_mailboxes(client, dimail_token_ok): # pylint: disable=W0613
|
||||
"""Test admin action to send pending mailboxes to dimail."""
|
||||
admin = core_factories.UserFactory(is_staff=True, is_superuser=True)
|
||||
client.force_login(admin)
|
||||
@@ -223,13 +218,7 @@ def test_send_pending_mailboxes(client):
|
||||
|
||||
url = reverse("admin:mailbox_manager_maildomain_changelist")
|
||||
for mailbox in mailboxes:
|
||||
responses.add(
|
||||
responses.GET,
|
||||
re.compile(r".*/token/"),
|
||||
body=TOKEN_OK,
|
||||
status=status.HTTP_200_OK,
|
||||
content_type="application/json",
|
||||
)
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
||||
@@ -247,7 +236,7 @@ def test_send_pending_mailboxes(client):
|
||||
|
||||
@responses.activate
|
||||
@pytest.mark.django_db
|
||||
def test_send_pending_mailboxes__listing_failed_mailboxes(client):
|
||||
def test_send_pending_mailboxes__listing_failed_mailboxes(client, dimail_token_ok): # pylint: disable=W0613
|
||||
"""Test admin action to send pending mailboxes to dimail."""
|
||||
admin = core_factories.UserFactory(is_staff=True, is_superuser=True)
|
||||
client.force_login(admin)
|
||||
@@ -261,13 +250,7 @@ def test_send_pending_mailboxes__listing_failed_mailboxes(client):
|
||||
}
|
||||
|
||||
url = reverse("admin:mailbox_manager_maildomain_changelist")
|
||||
responses.add(
|
||||
responses.GET,
|
||||
re.compile(r".*/token/"),
|
||||
body=TOKEN_OK,
|
||||
status=status.HTTP_200_OK,
|
||||
content_type="application/json",
|
||||
)
|
||||
# token response in fixtures
|
||||
responses.add(
|
||||
responses.POST,
|
||||
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
||||
|
||||
@@ -199,6 +199,12 @@ def test_dimail_synchronization__synchronize_aliases(dimail_token_ok): # pylint
|
||||
"destination": existing_mailbox.secondary_email,
|
||||
"allow_to_send": False,
|
||||
},
|
||||
{ # alias to devnull@devnull
|
||||
"username": "spam",
|
||||
"domain": alias.domain.name,
|
||||
"destination": "devnull@devnull",
|
||||
"allow_to_send": False,
|
||||
},
|
||||
]
|
||||
responses.get(
|
||||
re.compile(rf".*/domains/{alias.domain.name}/aliases/"),
|
||||
@@ -209,8 +215,8 @@ def test_dimail_synchronization__synchronize_aliases(dimail_token_ok): # pylint
|
||||
|
||||
imported_aliases = dimail_client.import_aliases(alias.domain)
|
||||
|
||||
assert len(imported_aliases) == 3
|
||||
assert models.Alias.objects.count() == 4
|
||||
assert len(imported_aliases) == 4
|
||||
assert models.Alias.objects.count() == 5
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -173,9 +173,6 @@ class Base(Configuration):
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [
|
||||
os.path.join(BASE_DIR, "templates"),
|
||||
os.path.join(
|
||||
BASE_DIR, "admin", "templates"
|
||||
), # enforce load before Django's admin
|
||||
],
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
@@ -227,7 +224,6 @@ class Base(Configuration):
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# People
|
||||
"admin.apps.PeopleAdminConfig", # replaces 'django.contrib.admin'
|
||||
"core",
|
||||
"demo",
|
||||
"mailbox_manager.apps.MailboxManagerConfig",
|
||||
@@ -246,6 +242,7 @@ class Base(Configuration):
|
||||
"parler",
|
||||
"rest_framework",
|
||||
"treebeard",
|
||||
"lasuite.admin", # must be before django.contrib.admin
|
||||
# Django
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
@@ -254,6 +251,7 @@ class Base(Configuration):
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.admin",
|
||||
# OIDC third party
|
||||
"mozilla_django_oidc",
|
||||
]
|
||||
|
||||
+25
-25
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "people"
|
||||
version = "1.22.0"
|
||||
version = "1.23.1"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -17,51 +17,51 @@ classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
description = "An application to handle contacts and teams."
|
||||
keywords = ["Django", "Contacts", "Teams", "RBAC"]
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
requires-python = "~=3.14.2"
|
||||
dependencies = [
|
||||
"Brotli==1.2.0",
|
||||
"PyJWT==2.10.1",
|
||||
"boto3==1.42.24",
|
||||
"PyJWT==2.12.1",
|
||||
"boto3==1.42.68",
|
||||
"celery[redis]==5.6.2",
|
||||
"django-celery-beat==2.8.1",
|
||||
"django-celery-beat==2.9.0",
|
||||
"django-celery-results==2.6.0",
|
||||
"django-configurations==2.5.1",
|
||||
"django-cors-headers==4.9.0",
|
||||
"django-countries==8.2.0",
|
||||
"django-extensions==4.1",
|
||||
"django-lasuite==0.0.22",
|
||||
"django-lasuite==0.0.25",
|
||||
"django-oauth-toolkit==3.2.0",
|
||||
"django-parler==2.3",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages==1.14.6",
|
||||
"django-timezone-field>=5.1",
|
||||
"django-treebeard==4.8.0",
|
||||
"django-zxcvbn-password-validator==1.5.2",
|
||||
"django==5.2.9",
|
||||
"django-treebeard==5.0.5",
|
||||
"django-zxcvbn-password-validator==1.5.3",
|
||||
"django==6.0.3",
|
||||
"djangorestframework==3.16.1",
|
||||
"dockerflow==2024.4.2",
|
||||
"dockerflow==2026.3.4",
|
||||
"drf_spectacular==0.29.0",
|
||||
"drf_spectacular[sidecar]==0.29.0",
|
||||
"easy_thumbnails==2.10.1",
|
||||
"factory_boy==3.3.3",
|
||||
"flower==2.0.1",
|
||||
"gunicorn==23.0.0",
|
||||
"gunicorn==25.1.0",
|
||||
"jaraco.context>=6.1.0",
|
||||
"joserfc==1.6.1",
|
||||
"joserfc==1.6.3",
|
||||
"jsonschema==4.26.0",
|
||||
"mozilla-django-oidc==5.0.2",
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"psycopg[binary]==3.3.2",
|
||||
"redis<=7.1.0",
|
||||
"psycopg[binary]==3.3.3",
|
||||
"redis<=7.3.0",
|
||||
"requests==2.32.5",
|
||||
"sentry-sdk[django]==2.49.0",
|
||||
"whitenoise==6.11.0",
|
||||
"sentry-sdk[django]==2.54.0",
|
||||
"whitenoise==6.12.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -72,20 +72,20 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"drf-spectacular-sidecar==2026.1.1",
|
||||
"drf-spectacular-sidecar==2026.3.1",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.9.0",
|
||||
"jq==1.10.0",
|
||||
"pyfakefs==6.0.0",
|
||||
"ipython==9.11.0",
|
||||
"jq==1.11.0",
|
||||
"pyfakefs==6.1.5",
|
||||
"pylint-django==2.7.0",
|
||||
"pylint==4.0.4",
|
||||
"pylint==4.0.5",
|
||||
"pytest-cov==7.0.0",
|
||||
"pytest-django==4.11.1",
|
||||
"pytest-django==4.12.0",
|
||||
"pytest==9.0.2",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.8.0",
|
||||
"responses==0.25.8",
|
||||
"ruff==0.14.11",
|
||||
"responses==0.26.0",
|
||||
"ruff==0.15.6",
|
||||
"types-requests==2.32.4.20260107",
|
||||
"freezegun==1.5.5",
|
||||
]
|
||||
|
||||
Generated
+1803
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,864 @@
|
||||
import { cunninghamConfig } from '@gouvfr-lasuite/ui-kit';
|
||||
import { getThemesFromGlobals } from '@gouvfr-lasuite/cunningham-tokens';
|
||||
|
||||
export default {
|
||||
...cunninghamConfig,
|
||||
export const commonTokenOverrides = {
|
||||
components: {
|
||||
favicon: {
|
||||
'png-light': '/assets/favicon-light.png',
|
||||
'png-dark': '/assets/favicon-dark.png',
|
||||
},
|
||||
modal: {
|
||||
'width-small': '342px',
|
||||
},
|
||||
tooltip: {
|
||||
padding: '4px 8px',
|
||||
},
|
||||
button: {
|
||||
'medium-height': '40px',
|
||||
'medium-text-height': '40px',
|
||||
'border-radius': '4px',
|
||||
'border-radius--active': '4px',
|
||||
'border-radius--focus': '4px',
|
||||
},
|
||||
datagrid: {
|
||||
'header--color': 'ref(contextuals.content.semantic.neutral.primary)',
|
||||
'header--size': '12px',
|
||||
'header--weight': '500',
|
||||
'body--background-color-hover':
|
||||
'ref(contextuals.background.semantic.neutral.tertiary)',
|
||||
},
|
||||
'forms-checkbox': {
|
||||
'font-size': 'ref(globals.font.sizes.sm)',
|
||||
},
|
||||
badge: {
|
||||
'font-size': 'ref(globals.font.sizes.xs)',
|
||||
'border-radius': '12px',
|
||||
'padding-inline': 'ref(globals.spacings.xs)',
|
||||
'padding-block': 'ref(globals.spacings.2xs)',
|
||||
accent: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.brand.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.brand.secondary)',
|
||||
},
|
||||
neutral: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.neutral.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.neutral.secondary)',
|
||||
},
|
||||
danger: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.error.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.error.secondary)',
|
||||
},
|
||||
success: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.success.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.success.secondary)',
|
||||
},
|
||||
warning: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.warning.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.warning.secondary)',
|
||||
},
|
||||
info: {
|
||||
'background-color':
|
||||
'ref(contextuals.background.semantic.info.secondary)',
|
||||
color: 'ref(contextuals.content.semantic.info.secondary)',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const commonGlobals = {
|
||||
components: {
|
||||
'la-gaufre': false,
|
||||
'home-proconnect': false,
|
||||
logo: {
|
||||
src: '',
|
||||
alt: '',
|
||||
widthHeader: '',
|
||||
widthFooter: '',
|
||||
},
|
||||
},
|
||||
font: {
|
||||
sizes: {
|
||||
xs: '0.75rem',
|
||||
sm: '0.875rem',
|
||||
md: '1rem',
|
||||
lg: '1.125rem',
|
||||
ml: '0.938rem',
|
||||
xl: '1.25rem',
|
||||
t: '0.6875rem',
|
||||
s: '0.75rem',
|
||||
h1: '2rem',
|
||||
h2: '1.75rem',
|
||||
h3: '1.5rem',
|
||||
h4: '1.375rem',
|
||||
h5: '1.25rem',
|
||||
h6: '1.125rem',
|
||||
'xl-alt': '5rem',
|
||||
'lg-alt': '4.5rem',
|
||||
'md-alt': '4rem',
|
||||
'sm-alt': '3.5rem',
|
||||
'xs-alt': '3rem',
|
||||
},
|
||||
weights: {
|
||||
thin: 100,
|
||||
extrabold: 800,
|
||||
black: 900,
|
||||
},
|
||||
families: {
|
||||
accent: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
|
||||
base: 'Marianne, Inter, Roboto Flex Variable, sans-serif',
|
||||
},
|
||||
},
|
||||
spacings: {
|
||||
'0': '0',
|
||||
none: '0',
|
||||
auto: 'auto',
|
||||
bx: '2.2rem',
|
||||
full: '100%',
|
||||
'4xs': '0.125rem',
|
||||
'3xs': '0.25rem',
|
||||
'2xs': '0.375rem',
|
||||
xs: '0.5rem',
|
||||
sm: '0.75rem',
|
||||
base: '1rem',
|
||||
md: '1.5rem',
|
||||
lg: '2rem',
|
||||
xl: '2.5rem',
|
||||
xxl: '3rem',
|
||||
'2xl': '3rem',
|
||||
xxxl: '3.5rem',
|
||||
'3xl': '3.5rem',
|
||||
'4xl': '4rem',
|
||||
'5xl': '4.5rem',
|
||||
'6xl': '6rem',
|
||||
'7xl': '7.5rem',
|
||||
},
|
||||
breakpoints: {
|
||||
xxs: '320px',
|
||||
xs: '480px',
|
||||
mobile: '768px',
|
||||
tablet: '1024px',
|
||||
},
|
||||
};
|
||||
|
||||
export const whiteLabelGlobals = {
|
||||
colors: {
|
||||
'logo-1-light': '#4844AD',
|
||||
'logo-2-light': '#4844AD',
|
||||
'logo-1-dark': '#BEC5F0',
|
||||
'logo-2-dark': '#BEC5F0',
|
||||
'brand-050': '#EEF1FA',
|
||||
'brand-100': '#DDE2F5',
|
||||
'brand-150': '#CED3F1',
|
||||
'brand-200': '#BEC5F0',
|
||||
'brand-250': '#AFB5F1',
|
||||
'brand-300': '#A0A5F6',
|
||||
'brand-350': '#8F94FD',
|
||||
'brand-400': '#8184FC',
|
||||
'brand-450': '#7576EE',
|
||||
'brand-500': '#6969DF',
|
||||
'brand-550': '#5E5CD0',
|
||||
'brand-600': '#534FC2',
|
||||
'brand-650': '#4844AD',
|
||||
'brand-700': '#3E3B98',
|
||||
'brand-750': '#36347D',
|
||||
'brand-800': '#2D2F5F',
|
||||
'brand-850': '#262848',
|
||||
'brand-900': '#1C1E32',
|
||||
'brand-950': '#11131F',
|
||||
'gray-000': '#FFFFFF',
|
||||
'gray-025': '#F8F8F9',
|
||||
'gray-050': '#F0F0F3',
|
||||
'gray-100': '#E2E2EA',
|
||||
'gray-150': '#D3D4E0',
|
||||
'gray-200': '#C5C6D5',
|
||||
'gray-250': '#B7B7CB',
|
||||
'gray-300': '#A9A9BF',
|
||||
'gray-350': '#9C9CB2',
|
||||
'gray-400': '#8F8FA4',
|
||||
'gray-450': '#828297',
|
||||
'gray-500': '#75758A',
|
||||
'gray-550': '#69697D',
|
||||
'gray-600': '#5D5D70',
|
||||
'gray-650': '#515164',
|
||||
'gray-700': '#454558',
|
||||
'gray-750': '#3A3A4C',
|
||||
'gray-800': '#2F303D',
|
||||
'gray-850': '#25252F',
|
||||
'gray-900': '#1B1B23',
|
||||
'gray-950': '#111114',
|
||||
'gray-1000': '#000000',
|
||||
'info-050': '#EAF2F9',
|
||||
'info-100': '#D5E4F3',
|
||||
'info-150': '#BFD7F0',
|
||||
'info-200': '#A7CAEE',
|
||||
'info-250': '#8DBDEF',
|
||||
'info-300': '#6EB0F2',
|
||||
'info-350': '#50A2F5',
|
||||
'info-400': '#3593F4',
|
||||
'info-450': '#1185ED',
|
||||
'info-500': '#0077DE',
|
||||
'info-550': '#0069CF',
|
||||
'info-600': '#005BC0',
|
||||
'info-650': '#0D4EAA',
|
||||
'info-700': '#124394',
|
||||
'info-750': '#163878',
|
||||
'info-800': '#192F5A',
|
||||
'info-850': '#192541',
|
||||
'info-900': '#141B2D',
|
||||
'info-950': '#0C111C',
|
||||
'success-050': '#E8F1EA',
|
||||
'success-100': '#CFE4D4',
|
||||
'success-150': '#BAD9C1',
|
||||
'success-200': '#A2CFAD',
|
||||
'success-250': '#86C597',
|
||||
'success-300': '#6CBA83',
|
||||
'success-350': '#4FB070',
|
||||
'success-400': '#40A363',
|
||||
'success-450': '#309556',
|
||||
'success-500': '#1E884A',
|
||||
'success-550': '#027B3E',
|
||||
'success-600': '#016D31',
|
||||
'success-650': '#006024',
|
||||
'success-700': '#005317',
|
||||
'success-750': '#0D4511',
|
||||
'success-800': '#11380E',
|
||||
'success-850': '#132A11',
|
||||
'success-900': '#101E0F',
|
||||
'success-950': '#091209',
|
||||
'warning-050': '#F8F0E9',
|
||||
'warning-100': '#F1E0D3',
|
||||
'warning-150': '#ECD0BC',
|
||||
'warning-200': '#E8C0A4',
|
||||
'warning-250': '#E8AE8A',
|
||||
'warning-300': '#EB9970',
|
||||
'warning-350': '#E98456',
|
||||
'warning-400': '#E57036',
|
||||
'warning-450': '#DA5E18',
|
||||
'warning-500': '#CB5000',
|
||||
'warning-550': '#BC4200',
|
||||
'warning-600': '#AD3300',
|
||||
'warning-650': '#9E2300',
|
||||
'warning-700': '#882011',
|
||||
'warning-750': '#731E16',
|
||||
'warning-800': '#58201A',
|
||||
'warning-850': '#401D18',
|
||||
'warning-900': '#2E1714',
|
||||
'warning-950': '#1D0F0D',
|
||||
'error-050': '#F9EFEC',
|
||||
'error-100': '#F4DFD9',
|
||||
'error-150': '#F0CEC6',
|
||||
'error-200': '#EEBCB2',
|
||||
'error-250': '#EEA99D',
|
||||
'error-300': '#EF9486',
|
||||
'error-350': '#F37C6E',
|
||||
'error-400': '#F65F53',
|
||||
'error-450': '#F0463D',
|
||||
'error-500': '#E82322',
|
||||
'error-550': '#D7010E',
|
||||
'error-600': '#C00100',
|
||||
'error-650': '#AA0000',
|
||||
'error-700': '#910C06',
|
||||
'error-750': '#731E16',
|
||||
'error-800': '#58201A',
|
||||
'error-850': '#401D18',
|
||||
'error-900': '#2E1714',
|
||||
'error-950': '#1D0F0D',
|
||||
'red-050': '#FAEFEE',
|
||||
'red-100': '#F4DEDD',
|
||||
'red-150': '#F1CDCB',
|
||||
'red-200': '#EFBBBA',
|
||||
'red-250': '#EEA8A8',
|
||||
'red-300': '#F09394',
|
||||
'red-350': '#F37B7E',
|
||||
'red-400': '#EF6569',
|
||||
'red-450': '#E94A55',
|
||||
'red-500': '#DA3B49',
|
||||
'red-550': '#CA2A3C',
|
||||
'red-600': '#BB1330',
|
||||
'red-650': '#A90021',
|
||||
'red-700': '#910A13',
|
||||
'red-750': '#731E16',
|
||||
'red-800': '#58201A',
|
||||
'red-850': '#411D18',
|
||||
'red-900': '#2E1714',
|
||||
'red-950': '#1D0F0D',
|
||||
'orange-050': '#F8F0E9',
|
||||
'orange-100': '#F1E0D3',
|
||||
'orange-150': '#ECD0BD',
|
||||
'orange-200': '#EABFA6',
|
||||
'orange-250': '#EBAC90',
|
||||
'orange-300': '#EC9772',
|
||||
'orange-350': '#E5845A',
|
||||
'orange-400': '#D6774D',
|
||||
'orange-450': '#C86A40',
|
||||
'orange-500': '#B95D33',
|
||||
'orange-550': '#AB5025',
|
||||
'orange-600': '#9D4315',
|
||||
'orange-650': '#8F3600',
|
||||
'orange-700': '#812900',
|
||||
'orange-750': '#6C2511',
|
||||
'orange-800': '#572017',
|
||||
'orange-850': '#401D18',
|
||||
'orange-900': '#2E1714',
|
||||
'orange-950': '#1D0F0D',
|
||||
'brown-050': '#F6F0E8',
|
||||
'brown-100': '#F1E0D3',
|
||||
'brown-150': '#EBD0BA',
|
||||
'brown-200': '#E2C0A6',
|
||||
'brown-250': '#D4B398',
|
||||
'brown-300': '#C6A58B',
|
||||
'brown-350': '#B8987E',
|
||||
'brown-400': '#AA8B71',
|
||||
'brown-450': '#9D7E65',
|
||||
'brown-500': '#8F7158',
|
||||
'brown-550': '#82654C',
|
||||
'brown-600': '#765841',
|
||||
'brown-650': '#694C35',
|
||||
'brown-700': '#5D412A',
|
||||
'brown-750': '#51361E',
|
||||
'brown-800': '#452A13',
|
||||
'brown-850': '#392008',
|
||||
'brown-900': '#29180A',
|
||||
'brown-950': '#1B0F08',
|
||||
'yellow-050': '#F3F0E7',
|
||||
'yellow-100': '#E9E2CF',
|
||||
'yellow-150': '#E1D4B7',
|
||||
'yellow-200': '#D9C599',
|
||||
'yellow-250': '#D2B677',
|
||||
'yellow-300': '#CAA756',
|
||||
'yellow-350': '#C2972E',
|
||||
'yellow-400': '#B98900',
|
||||
'yellow-450': '#AB7B00',
|
||||
'yellow-500': '#9D6E00',
|
||||
'yellow-550': '#916100',
|
||||
'yellow-600': '#855400',
|
||||
'yellow-650': '#784700',
|
||||
'yellow-700': '#6C3A00',
|
||||
'yellow-750': '#5F2E00',
|
||||
'yellow-800': '#512302',
|
||||
'yellow-850': '#3E1D10',
|
||||
'yellow-900': '#2D1711',
|
||||
'yellow-950': '#1D0F0D',
|
||||
'green-050': '#E6F1E9',
|
||||
'green-100': '#CFE4D5',
|
||||
'green-150': '#B8D8C1',
|
||||
'green-200': '#A0CFAE',
|
||||
'green-250': '#84C59A',
|
||||
'green-300': '#65BA86',
|
||||
'green-350': '#45B173',
|
||||
'green-400': '#23A562',
|
||||
'green-450': '#029755',
|
||||
'green-500': '#008948',
|
||||
'green-550': '#017B3B',
|
||||
'green-600': '#006E2E',
|
||||
'green-650': '#006022',
|
||||
'green-700': '#005314',
|
||||
'green-750': '#0D4510',
|
||||
'green-800': '#11380E',
|
||||
'green-850': '#132A11',
|
||||
'green-900': '#101E0F',
|
||||
'green-950': '#091209',
|
||||
'blue-1-050': '#EBF1F9',
|
||||
'blue-1-100': '#D6E4F4',
|
||||
'blue-1-150': '#C1D7F0',
|
||||
'blue-1-200': '#AACAEF',
|
||||
'blue-1-250': '#8FBCEF',
|
||||
'blue-1-300': '#7CAFEB',
|
||||
'blue-1-350': '#68A1E4',
|
||||
'blue-1-400': '#5B94D6',
|
||||
'blue-1-450': '#4E86C7',
|
||||
'blue-1-500': '#4279B9',
|
||||
'blue-1-550': '#356CAC',
|
||||
'blue-1-600': '#28609E',
|
||||
'blue-1-650': '#1B5390',
|
||||
'blue-1-700': '#0B4783',
|
||||
'blue-1-750': '#0F3C6E',
|
||||
'blue-1-800': '#133059',
|
||||
'blue-1-850': '#152641',
|
||||
'blue-1-900': '#121C2D',
|
||||
'blue-1-950': '#0B111C',
|
||||
'blue-2-050': '#E7F3F4',
|
||||
'blue-2-100': '#CEE7E9',
|
||||
'blue-2-150': '#B2DCE0',
|
||||
'blue-2-200': '#91D1D7',
|
||||
'blue-2-250': '#68C7D0',
|
||||
'blue-2-300': '#43BBC5',
|
||||
'blue-2-350': '#00AFBA',
|
||||
'blue-2-400': '#01A0AA',
|
||||
'blue-2-450': '#00929D',
|
||||
'blue-2-500': '#00848F',
|
||||
'blue-2-550': '#007682',
|
||||
'blue-2-600': '#016874',
|
||||
'blue-2-650': '#005B67',
|
||||
'blue-2-700': '#004E5A',
|
||||
'blue-2-750': '#00424E',
|
||||
'blue-2-800': '#003642',
|
||||
'blue-2-850': '#002A38',
|
||||
'blue-2-900': '#061E28',
|
||||
'blue-2-950': '#071219',
|
||||
'purple-050': '#F7F0F6',
|
||||
'purple-100': '#EEE0EE',
|
||||
'purple-150': '#E7D1E7',
|
||||
'purple-200': '#DBBFE4',
|
||||
'purple-250': '#D3AEE2',
|
||||
'purple-300': '#CB99E1',
|
||||
'purple-350': '#C188D9',
|
||||
'purple-400': '#B47BCB',
|
||||
'purple-450': '#A66EBD',
|
||||
'purple-500': '#9961AF',
|
||||
'purple-550': '#8B55A1',
|
||||
'purple-600': '#7E4894',
|
||||
'purple-650': '#723C87',
|
||||
'purple-700': '#633376',
|
||||
'purple-750': '#552A65',
|
||||
'purple-800': '#452551',
|
||||
'purple-850': '#35213D',
|
||||
'purple-900': '#261A2C',
|
||||
'purple-950': '#17111C',
|
||||
'pink-050': '#F8EFF4',
|
||||
'pink-100': '#F0DFEA',
|
||||
'pink-150': '#EACEDF',
|
||||
'pink-200': '#E9BBD1',
|
||||
'pink-250': '#E9A7C2',
|
||||
'pink-300': '#E095B4',
|
||||
'pink-350': '#D685A8',
|
||||
'pink-400': '#C7799B',
|
||||
'pink-450': '#B86C8D',
|
||||
'pink-500': '#AA5F80',
|
||||
'pink-550': '#9C5374',
|
||||
'pink-600': '#8E4767',
|
||||
'pink-650': '#813B5B',
|
||||
'pink-700': '#732E4F',
|
||||
'pink-750': '#632643',
|
||||
'pink-800': '#521F38',
|
||||
'pink-850': '#3E1C2B',
|
||||
'pink-900': '#2D171F',
|
||||
'pink-950': '#1C0E12',
|
||||
'black-000': '#1B1B2300',
|
||||
'black-050': '#1B1B230D',
|
||||
'black-100': '#1B1B231A',
|
||||
'black-150': '#1B1B2326',
|
||||
'black-200': '#1B1B2333',
|
||||
'black-250': '#1B1B2340',
|
||||
'black-300': '#1B1B234D',
|
||||
'black-350': '#1B1B2359',
|
||||
'black-400': '#1B1B2366',
|
||||
'black-450': '#1B1B2373',
|
||||
'black-500': '#1B1B2380',
|
||||
'black-550': '#1B1B238C',
|
||||
'black-600': '#1B1B2399',
|
||||
'black-650': '#1B1B23A6',
|
||||
'black-700': '#1B1B23B2',
|
||||
'black-750': '#1B1B23BF',
|
||||
'black-800': '#1B1B23CC',
|
||||
'black-850': '#1B1B23D9',
|
||||
'black-900': '#1B1B23E5',
|
||||
'black-950': '#111114F2',
|
||||
'white-000': '#F8F8F900',
|
||||
'white-050': '#F8F8F90D',
|
||||
'white-100': '#F8F8F91A',
|
||||
'white-150': '#F8F8F926',
|
||||
'white-200': '#F8F8F933',
|
||||
'white-250': '#F8F8F940',
|
||||
'white-300': '#F8F8F94D',
|
||||
'white-350': '#F8F8F959',
|
||||
'white-400': '#F8F8F966',
|
||||
'white-450': '#F8F8F973',
|
||||
'white-500': '#F8F8F980',
|
||||
'white-550': '#F8F8F98C',
|
||||
'white-600': '#F8F8F999',
|
||||
'white-650': '#F8F8F9A6',
|
||||
'white-700': '#F8F8F9B2',
|
||||
'white-750': '#F8F8F9BF',
|
||||
'white-800': '#F8F8F9CC',
|
||||
'white-850': '#F8F8F9D9',
|
||||
'white-900': '#F8F8F9E5',
|
||||
'white-950': '#F8F8F9F2',
|
||||
'white-975': '#F8F8F9F9',
|
||||
},
|
||||
...commonGlobals,
|
||||
font: {
|
||||
...commonGlobals.font,
|
||||
families: {
|
||||
base: 'Hanken Grotesk, Inter, Roboto Flex Variable, sans-serif',
|
||||
accent: 'Hanken Grotesk, Inter, Roboto Flex Variable, sans-serif',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const dsfrGlobals = {
|
||||
...commonGlobals,
|
||||
components: {
|
||||
...commonGlobals.components,
|
||||
'la-gaufre': true,
|
||||
'home-proconnect': true,
|
||||
logo: {
|
||||
src: '/assets/logo-gouv.svg',
|
||||
widthHeader: '110px',
|
||||
widthFooter: '220px',
|
||||
alt: 'Gouvernement Logo',
|
||||
},
|
||||
},
|
||||
colors: {
|
||||
'logo-1': '#2845C1',
|
||||
'logo-2': '#C83F49',
|
||||
'brand-050': '#EDF0FF',
|
||||
'brand-100': '#DAE2FF',
|
||||
'brand-150': '#C8D3FF',
|
||||
'brand-200': '#B5C4FF',
|
||||
'brand-250': '#A2B6FF',
|
||||
'brand-300': '#90A7FF',
|
||||
'brand-350': '#7E98FF',
|
||||
'brand-400': '#6C89FE',
|
||||
'brand-450': '#5C7AF7',
|
||||
'brand-500': '#4C6CEF',
|
||||
'brand-550': '#3E5DE7',
|
||||
'brand-600': '#304DDF',
|
||||
'brand-650': '#2845C1',
|
||||
'brand-700': '#223E9E',
|
||||
'brand-750': '#1F367D',
|
||||
'brand-800': '#1B2E5F',
|
||||
'brand-850': '#172446',
|
||||
'brand-900': '#121B30',
|
||||
'brand-950': '#0C111A',
|
||||
'gray-100': '#DFE2EA',
|
||||
'gray-150': '#CFD5DE',
|
||||
'gray-200': '#C1C7D3',
|
||||
'gray-250': '#B2B9C7',
|
||||
'gray-300': '#A4ABBC',
|
||||
'gray-350': '#969EB0',
|
||||
'gray-400': '#8891A4',
|
||||
'gray-450': '#7B8498',
|
||||
'gray-500': '#6D778C',
|
||||
'gray-550': '#626A80',
|
||||
'gray-600': '#555E74',
|
||||
'gray-650': '#4A5267',
|
||||
'gray-700': '#3F4759',
|
||||
'gray-750': '#363B4C',
|
||||
'gray-800': '#2B303D',
|
||||
'gray-850': '#222631',
|
||||
'gray-900': '#181B24',
|
||||
'gray-950': '#0F1117',
|
||||
'gray-1000': '#000000',
|
||||
'info-050': '#E7F2FF',
|
||||
'info-100': '#CFE5FF',
|
||||
'info-150': '#B7D7FF',
|
||||
'info-200': '#A0CAFE',
|
||||
'info-250': '#8CBCF9',
|
||||
'info-300': '#77AEF4',
|
||||
'info-350': '#63A0EE',
|
||||
'info-400': '#5092E7',
|
||||
'info-450': '#4185DC',
|
||||
'info-500': '#3677CC',
|
||||
'info-550': '#2F6ABB',
|
||||
'info-600': '#265EAA',
|
||||
'info-650': '#28528F',
|
||||
'info-700': '#274775',
|
||||
'info-750': '#243C5E',
|
||||
'info-800': '#20314A',
|
||||
'info-850': '#1B2637',
|
||||
'info-900': '#141C27',
|
||||
'info-950': '#0D1118',
|
||||
'success-050': '#DEF7E6',
|
||||
'success-100': '#BAEECF',
|
||||
'success-150': '#A5E2C0',
|
||||
'success-200': '#95D4B3',
|
||||
'success-250': '#85C6A7',
|
||||
'success-300': '#74B99B',
|
||||
'success-350': '#65AB8F',
|
||||
'success-400': '#579E84',
|
||||
'success-450': '#4B9079',
|
||||
'success-500': '#40836F',
|
||||
'success-550': '#367664',
|
||||
'success-600': '#2B695A',
|
||||
'success-650': '#2C5A50',
|
||||
'success-700': '#2A4D45',
|
||||
'success-750': '#26403C',
|
||||
'success-800': '#213430',
|
||||
'success-850': '#1B2826',
|
||||
'success-900': '#151D1C',
|
||||
'success-950': '#0D1212',
|
||||
'warning-050': '#FFEEDF',
|
||||
'warning-100': '#FFDCBE',
|
||||
'warning-150': '#FFCA9C',
|
||||
'warning-200': '#FFB778',
|
||||
'warning-250': '#FDA54F',
|
||||
'warning-300': '#F59425',
|
||||
'warning-350': '#E78613',
|
||||
'warning-400': '#D7790C',
|
||||
'warning-450': '#C86C08',
|
||||
'warning-500': '#B85F03',
|
||||
'warning-550': '#A75400',
|
||||
'warning-600': '#984800',
|
||||
'warning-650': '#814112',
|
||||
'warning-700': '#6C3A19',
|
||||
'warning-750': '#58321C',
|
||||
'warning-800': '#452A1A',
|
||||
'warning-850': '#352117',
|
||||
'warning-900': '#261813',
|
||||
'warning-950': '#170F0C',
|
||||
'error-050': '#FFEDEB',
|
||||
'error-100': '#FFDAD7',
|
||||
'error-150': '#FFC7C2',
|
||||
'error-200': '#FFB3AD',
|
||||
'error-250': '#FF9F99',
|
||||
'error-300': '#FF8984',
|
||||
'error-350': '#FF706E',
|
||||
'error-400': '#FB5759',
|
||||
'error-450': '#F63A45',
|
||||
'error-500': '#E32C39',
|
||||
'error-550': '#CF202D',
|
||||
'error-600': '#BD0F23',
|
||||
'error-650': '#9D2227',
|
||||
'error-700': '#812727',
|
||||
'error-750': '#672624',
|
||||
'error-800': '#512220',
|
||||
'error-850': '#3D1C1B',
|
||||
'error-900': '#2A1614',
|
||||
'error-950': '#190E0D',
|
||||
'red-050': '#FFEDEB',
|
||||
'red-100': '#FFDAD7',
|
||||
'red-150': '#FFC7C2',
|
||||
'red-200': '#FFB3AD',
|
||||
'red-250': '#FF9F99',
|
||||
'red-300': '#FF8984',
|
||||
'red-350': '#FF706E',
|
||||
'red-400': '#FB5759',
|
||||
'red-450': '#F63A45',
|
||||
'red-500': '#E32C39',
|
||||
'red-550': '#CF202D',
|
||||
'red-600': '#BD0F23',
|
||||
'red-650': '#9D2227',
|
||||
'red-700': '#812727',
|
||||
'red-750': '#672624',
|
||||
'red-800': '#512220',
|
||||
'red-850': '#3D1C1B',
|
||||
'red-900': '#410003',
|
||||
'red-950': '#190E0D',
|
||||
'orange-050': '#FCEDEB',
|
||||
'orange-100': '#F8DCD7',
|
||||
'orange-150': '#F1CCC5',
|
||||
'orange-200': '#EABCB4',
|
||||
'orange-250': '#E2ACA2',
|
||||
'orange-300': '#DA9C92',
|
||||
'orange-350': '#D28C81',
|
||||
'orange-400': '#CA7C70',
|
||||
'orange-450': '#BE6E62',
|
||||
'orange-500': '#AE6257',
|
||||
'orange-550': '#9E564D',
|
||||
'orange-600': '#8F4B42',
|
||||
'orange-650': '#79443D',
|
||||
'orange-700': '#643C37',
|
||||
'orange-750': '#513430',
|
||||
'orange-800': '#412B28',
|
||||
'orange-850': '#312220',
|
||||
'orange-900': '#231918',
|
||||
'orange-950': '#150F0F',
|
||||
'brown-050': '#F9EFEA',
|
||||
'brown-100': '#F3DFD3',
|
||||
'brown-150': '#EACFC1',
|
||||
'brown-200': '#E2BFAE',
|
||||
'brown-250': '#D8B19C',
|
||||
'brown-300': '#D0A189',
|
||||
'brown-350': '#C3937B',
|
||||
'brown-400': '#B5866D',
|
||||
'brown-450': '#A77A62',
|
||||
'brown-500': '#996D57',
|
||||
'brown-550': '#8B614D',
|
||||
'brown-600': '#7C5542',
|
||||
'brown-650': '#6A4C3C',
|
||||
'brown-700': '#594236',
|
||||
'brown-750': '#49382F',
|
||||
'brown-800': '#3B2E28',
|
||||
'brown-850': '#2D2420',
|
||||
'brown-900': '#201A18',
|
||||
'brown-950': '#13100F',
|
||||
'yellow-050': '#FDF1C5',
|
||||
'yellow-100': '#FBE18E',
|
||||
'yellow-150': '#F4D261',
|
||||
'yellow-200': '#EAC244',
|
||||
'yellow-250': '#DFB41B',
|
||||
'yellow-300': '#D1A516',
|
||||
'yellow-350': '#C49711',
|
||||
'yellow-400': '#B78A0C',
|
||||
'yellow-450': '#A87D07',
|
||||
'yellow-500': '#9B6F02',
|
||||
'yellow-550': '#8D6300',
|
||||
'yellow-600': '#7F5600',
|
||||
'yellow-650': '#6E4C11',
|
||||
'yellow-700': '#5D4219',
|
||||
'yellow-750': '#4D371B',
|
||||
'yellow-800': '#3D2E1A',
|
||||
'yellow-850': '#2F2417',
|
||||
'yellow-900': '#221A12',
|
||||
'yellow-950': '#14100C',
|
||||
'green-050': '#E7F9B3',
|
||||
'green-100': '#D5EC98',
|
||||
'green-150': '#C5DE86',
|
||||
'green-200': '#B5D174',
|
||||
'green-250': '#A5C464',
|
||||
'green-300': '#95B755',
|
||||
'green-350': '#85AA45',
|
||||
'green-400': '#769D39',
|
||||
'green-450': '#688F30',
|
||||
'green-500': '#5A8228',
|
||||
'green-550': '#4D7621',
|
||||
'green-600': '#416919',
|
||||
'green-650': '#3A5B20',
|
||||
'green-700': '#324E22',
|
||||
'green-750': '#2C4122',
|
||||
'green-800': '#24351D',
|
||||
'green-850': '#1D2919',
|
||||
'green-900': '#161E13',
|
||||
'green-950': '#0E120C',
|
||||
'blue-1-050': '#E7F2FF',
|
||||
'blue-1-100': '#CFE5FF',
|
||||
'blue-1-150': '#B7D7FF',
|
||||
'blue-1-200': '#A0CAFE',
|
||||
'blue-1-250': '#8CBCF9',
|
||||
'blue-1-300': '#77AEF4',
|
||||
'blue-1-350': '#63A0EE',
|
||||
'blue-1-400': '#5092E7',
|
||||
'blue-1-450': '#4185DC',
|
||||
'blue-1-500': '#3677CC',
|
||||
'blue-1-550': '#2F6ABB',
|
||||
'blue-1-600': '#265EAA',
|
||||
'blue-1-650': '#28528F',
|
||||
'blue-1-700': '#274775',
|
||||
'blue-1-750': '#243C5E',
|
||||
'blue-1-800': '#20314A',
|
||||
'blue-1-850': '#1B2637',
|
||||
'blue-1-900': '#141C27',
|
||||
'blue-1-950': '#0D1118',
|
||||
'blue-2-050': '#E2F4FD',
|
||||
'blue-2-100': '#C4E8F8',
|
||||
'blue-2-150': '#AADCF2',
|
||||
'blue-2-200': '#93CFEB',
|
||||
'blue-2-250': '#7CC2E2',
|
||||
'blue-2-300': '#6CB4D6',
|
||||
'blue-2-350': '#5CA7C9',
|
||||
'blue-2-400': '#5099BC',
|
||||
'blue-2-450': '#458BAE',
|
||||
'blue-2-500': '#3A7EA0',
|
||||
'blue-2-550': '#327191',
|
||||
'blue-2-600': '#286483',
|
||||
'blue-2-650': '#2B5770',
|
||||
'blue-2-700': '#294A5E',
|
||||
'blue-2-750': '#263E4D',
|
||||
'blue-2-800': '#22323D',
|
||||
'blue-2-850': '#1C272E',
|
||||
'blue-2-900': '#151D21',
|
||||
'blue-2-950': '#0E1114',
|
||||
'purple-050': '#F5EEFF',
|
||||
'purple-100': '#ECDCFF',
|
||||
'purple-150': '#E2CBFF',
|
||||
'purple-200': '#D9B9FF',
|
||||
'purple-250': '#D0A7FF',
|
||||
'purple-300': '#C894FE',
|
||||
'purple-350': '#BE83FA',
|
||||
'purple-400': '#B570F5',
|
||||
'purple-450': '#AB5EF0',
|
||||
'purple-500': '#A04BE8',
|
||||
'purple-550': '#933CDB',
|
||||
'purple-600': '#8530C8',
|
||||
'purple-650': '#7033A5',
|
||||
'purple-700': '#5D3185',
|
||||
'purple-750': '#4C2C6A',
|
||||
'purple-800': '#3C2652',
|
||||
'purple-850': '#2D203C',
|
||||
'purple-900': '#21182A',
|
||||
'purple-950': '#130F19',
|
||||
'pink-050': '#FFEBF6',
|
||||
'pink-100': '#FFD8ED',
|
||||
'pink-150': '#FCC4E3',
|
||||
'pink-200': '#F7B2D9',
|
||||
'pink-250': '#F29FCE',
|
||||
'pink-300': '#ED8CC3',
|
||||
'pink-350': '#E779B8',
|
||||
'pink-400': '#E264AD',
|
||||
'pink-450': '#D2579E',
|
||||
'pink-500': '#C24B8E',
|
||||
'pink-550': '#B0417F',
|
||||
'pink-600': '#9F3670',
|
||||
'pink-650': '#873560',
|
||||
'pink-700': '#6F3250',
|
||||
'pink-750': '#5A2C43',
|
||||
'pink-800': '#472635',
|
||||
'pink-850': '#351F29',
|
||||
'pink-900': '#26171D',
|
||||
'pink-950': '#170F12',
|
||||
'black-000': '#181B2400',
|
||||
'black-050': '#181B240D',
|
||||
'black-100': '#181B241A',
|
||||
'black-150': '#181B2426',
|
||||
'black-200': '#181B2433',
|
||||
'black-250': '#181B2440',
|
||||
'black-300': '#181B244D',
|
||||
'black-350': '#181B2459',
|
||||
'black-400': '#181B2466',
|
||||
'black-450': '#181B2473',
|
||||
'black-500': '#181B2480',
|
||||
'black-550': '#181B248C',
|
||||
'black-600': '#181B2499',
|
||||
'black-650': '#181B24A6',
|
||||
'black-700': '#181B24B2',
|
||||
'black-750': '#181B24BF',
|
||||
'black-800': '#181B24CC',
|
||||
'black-850': '#181B24D9',
|
||||
'black-900': '#181B24E5',
|
||||
'black-950': '#0F1117F2',
|
||||
'white-000': '#F6F8F900',
|
||||
'white-050': '#F6F8F90D',
|
||||
'white-100': '#F6F8F91A',
|
||||
'white-150': '#F6F8F926',
|
||||
'white-200': '#F6F8F933',
|
||||
'white-250': '#F6F8F940',
|
||||
'white-300': '#F6F8F94D',
|
||||
'white-350': '#F6F8F959',
|
||||
'white-400': '#F6F8F966',
|
||||
'white-450': '#F6F8F973',
|
||||
'white-500': '#F6F8F980',
|
||||
'white-550': '#F6F8F98C',
|
||||
'white-600': '#F6F8F999',
|
||||
'white-650': '#F6F8F9A6',
|
||||
'white-700': '#F6F8F9B2',
|
||||
'white-750': '#F6F8F9BF',
|
||||
'white-800': '#F6F8F9CC',
|
||||
'white-850': '#F6F8F9D9',
|
||||
'white-900': '#F6F8F9E5',
|
||||
'white-950': '#F6F8F9F2',
|
||||
'white-975': '#F6F8F9F9',
|
||||
},
|
||||
};
|
||||
|
||||
const whiteLabelThemes = getThemesFromGlobals(whiteLabelGlobals, {
|
||||
overrides: commonTokenOverrides,
|
||||
});
|
||||
const dsfrThemes = getThemesFromGlobals(dsfrGlobals, {
|
||||
overrides: commonTokenOverrides,
|
||||
});
|
||||
|
||||
if (dsfrThemes.dark) {
|
||||
dsfrThemes.dark.globals.components.logo.src =
|
||||
'/assets/logo-gouv-darkmode.svg';
|
||||
dsfrThemes.dark.globals.colors['logo-1'] = '#95ABFF';
|
||||
dsfrThemes.dark.globals.colors['logo-2'] = '#E78087';
|
||||
}
|
||||
|
||||
console.log(dsfrThemes);
|
||||
|
||||
const themes = {
|
||||
themes: {
|
||||
default: whiteLabelThemes.light,
|
||||
dark: whiteLabelThemes.dark,
|
||||
dsfr: dsfrThemes.light,
|
||||
'dsfr-dark': dsfrThemes.dark,
|
||||
},
|
||||
};
|
||||
|
||||
const config = { ...themes };
|
||||
|
||||
export default config;
|
||||
|
||||
Vendored
+1
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-desk",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -15,16 +15,17 @@
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gouvfr-lasuite/cunningham-react": "4.2.0",
|
||||
"@gouvfr-lasuite/cunningham-tokens": "^3.1.0",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "^0.17.0",
|
||||
"@gouvfr-lasuite/ui-kit": "0.19.10",
|
||||
"@hookform/resolvers": "4.0.0",
|
||||
"@openfun/cunningham-react": "3.2.3",
|
||||
"@tanstack/react-query": "5.90.5",
|
||||
"i18next": "25.6.0",
|
||||
"i18next-browser-languagedetector": "8.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"lodash": "4.17.23",
|
||||
"luxon": "3.7.2",
|
||||
"next": "15.4.10",
|
||||
"next": "15.5.14",
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-hook-form": "7.65.0",
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Page', () => {
|
||||
|
||||
render(<Page />, { wrapper: AppWrapper });
|
||||
|
||||
expect(mockedPush).toHaveBeenCalledWith('/teams/');
|
||||
expect(mockedPush).toHaveBeenCalledWith('/mail-domains/');
|
||||
});
|
||||
|
||||
it('checks Page rendering without team feature', () => {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M4.04601 20.3381C3.04306 20.3381 2.28465 20.0812 1.77079 19.5673C1.25693 19.0597 1 18.3105 1 17.32V6.01815C1 5.02139 1.25693 4.26917 1.77079 3.7615C2.28465 3.25383 3.04306 3 4.04601 3H19.9447C20.9538 3 21.7154 3.25383 22.2292 3.7615C22.7431 4.26917 23 5.02139 23 6.01815V17.32C23 18.3105 22.7431 19.0597 22.2292 19.5673C21.7154 20.0812 20.9538 20.3381 19.9447 20.3381H4.04601ZM4.15745 18.5087H19.8425C20.2697 18.5087 20.5979 18.3972 20.8269 18.1743C21.056 17.9515 21.1705 17.614 21.1705 17.1621V6.16674C21.1705 5.72098 21.056 5.38666 20.8269 5.16378C20.5979 4.9409 20.2697 4.82946 19.8425 4.82946H4.15745C3.72407 4.82946 3.39285 4.9409 3.16378 5.16378C2.9409 5.38666 2.82946 5.72098 2.82946 6.16674V17.1621C2.82946 17.614 2.9409 17.9515 3.16378 18.1743C3.39285 18.3972 3.72407 18.5087 4.15745 18.5087ZM8.38286 18.8058V4.51372H10.1195V18.8058H8.38286ZM6.56268 8.22837H4.65893C4.49796 8.22837 4.35556 8.16646 4.23174 8.04263C4.11411 7.91881 4.0553 7.78261 4.0553 7.63402C4.0553 7.47305 4.11411 7.33376 4.23174 7.21613C4.35556 7.09849 4.49796 7.03968 4.65893 7.03968H6.56268C6.72984 7.03968 6.87224 7.09849 6.98987 7.21613C7.11369 7.33376 7.1756 7.47305 7.1756 7.63402C7.1756 7.78261 7.11369 7.91881 6.98987 8.04263C6.87224 8.16646 6.72984 8.22837 6.56268 8.22837ZM6.56268 10.7172H4.65893C4.49796 10.7172 4.35556 10.6584 4.23174 10.5407C4.11411 10.4169 4.0553 10.2745 4.0553 10.1136C4.0553 9.95877 4.11411 9.82257 4.23174 9.70494C4.35556 9.58731 4.49796 9.52849 4.65893 9.52849H6.56268C6.72984 9.52849 6.87224 9.58731 6.98987 9.70494C7.11369 9.82257 7.1756 9.95877 7.1756 10.1136C7.1756 10.2745 7.11369 10.4169 6.98987 10.5407C6.87224 10.6584 6.72984 10.7172 6.56268 10.7172ZM6.56268 13.1967H4.65893C4.49796 13.1967 4.35556 13.1379 4.23174 13.0203C4.11411 12.9026 4.0553 12.7664 4.0553 12.6117C4.0553 12.4507 4.11411 12.3114 4.23174 12.1938C4.35556 12.0761 4.49796 12.0173 4.65893 12.0173H6.56268C6.72984 12.0173 6.87224 12.0761 6.98987 12.1938C7.11369 12.3114 7.1756 12.4507 7.1756 12.6117C7.1756 12.7664 7.11369 12.9026 6.98987 13.0203C6.87224 13.1379 6.72984 13.1967 6.56268 13.1967Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,6 +1,13 @@
|
||||
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1196 5.07095L17.3203 5.98796C17.4154 6.00474 17.4706 6.10543 17.4328 6.19433C17.2714 6.57329 17.1581 6.80005 17.0539 6.96275C16.9667 7.09909 16.8998 7.16937 16.8295 7.22281C16.7557 7.27883 16.6168 7.3625 16.3363 7.44595C15.0129 7.83959 14.2592 9.23154 14.6528 10.5549C15.0465 11.8784 16.4384 12.6321 17.7618 12.2384C19.4239 11.744 20.5126 10.8341 21.2653 9.65807C21.5989 9.13681 21.842 8.60181 22.0329 8.15355C22.1128 7.966 22.1849 7.79074 22.2525 7.62637L22.254 7.62267C22.4259 7.20484 22.8671 6.96601 23.312 7.04447C24.401 7.23649 25.1671 7.65773 25.6102 8.30817C26.0604 8.95985 26.1888 9.83372 25.9956 10.9298L23.0029 27.9022C22.8084 29.0053 22.3888 29.7825 21.7441 30.2339C21.1065 30.6865 20.2432 30.8168 19.1541 30.6248L7.96177 28.6513C6.87274 28.4593 6.10253 28.0409 5.65114 27.3963C5.20677 26.7529 5.08183 25.8796 5.27634 24.7765L8.26903 7.80411C8.4623 6.70806 8.87836 5.9302 9.51723 5.47054C10.1631 5.01212 11.0306 4.87892 12.1196 5.07095ZM13.2049 19.4392C13.6814 20.1198 14.3259 20.5317 15.1384 20.675C15.9509 20.8182 16.6974 20.6516 17.378 20.175C18.0586 19.6985 18.4705 19.054 18.6138 18.2415C18.757 17.429 18.5904 16.6825 18.1138 16.0019C17.6373 15.3213 16.9927 14.9094 16.1803 14.7661C15.3678 14.6229 14.6213 14.7895 13.9407 15.2661C13.2601 15.7426 12.8482 16.3872 12.7049 17.1996C12.5616 18.0121 12.7283 18.7586 13.2049 19.4392ZM8.27886 25.0247C8.19255 25.5142 8.5194 25.981 9.00891 26.0673L19.0539 27.8385C19.5434 27.9248 20.0102 27.598 20.0966 27.1084L20.3049 25.9267C20.3787 25.5081 20.3389 25.1045 20.1853 24.7156C20.0317 24.3268 19.7927 24.0086 19.4683 23.761C18.7724 23.2448 18.0473 22.8218 17.2931 22.4921C16.5389 22.1624 15.7555 21.9259 14.9431 21.7826C14.1306 21.6394 13.3136 21.5937 12.4921 21.6456C11.6707 21.6975 10.8446 21.8469 10.0141 22.094C9.62458 22.2157 9.29116 22.433 9.01387 22.7458C8.73658 23.0587 8.56104 23.4244 8.48724 23.8429L8.27886 25.0247Z" fill="#000091"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.694 3.88339C24.7066 4.43554 24.2692 4.89337 23.7171 4.90599C23.4094 4.91303 22.8638 4.99795 22.3424 5.23172C21.8251 5.46364 21.4224 5.79861 21.219 6.25464C21.0916 6.54023 20.9867 6.79533 20.88 7.05474C20.8115 7.22114 20.7424 7.38931 20.6661 7.56844C20.4842 7.99554 20.2799 8.43826 20.0151 8.85213C19.4512 9.73317 18.6551 10.4143 17.3473 10.8033C16.818 10.9608 16.2612 10.6593 16.1037 10.1299C15.9463 9.60059 16.2478 9.04381 16.7771 8.88635C17.6126 8.63784 18.0184 8.26165 18.3305 7.774C18.5036 7.50353 18.6559 7.18409 18.826 6.78475C18.8824 6.65241 18.9442 6.50248 19.01 6.34276C19.1273 6.05827 19.2574 5.7427 19.3924 5.44002C19.8608 4.38982 20.7275 3.76397 21.5241 3.40676C22.3167 3.05141 23.1294 2.91891 23.6714 2.90652C24.2235 2.89389 24.6813 3.33125 24.694 3.88339Z" fill="#C9191E"/>
|
||||
<path d="M12.9899 3.0145C12.4754 2.81379 12.221 2.23399 12.4217 1.71946C12.6224 1.20494 13.2022 0.950538 13.7167 1.15125C14.2217 1.34824 14.9402 1.75073 15.5634 2.35572C16.0249 2.80379 16.4723 3.40191 16.7083 4.14453C16.7414 4.2487 16.6503 4.34682 16.5427 4.32784L14.5057 3.96866C14.3783 3.9462 14.2631 3.88087 14.1703 3.79076C13.7603 3.39276 13.2766 3.12635 12.9899 3.0145Z" fill="#C9191E"/>
|
||||
<path d="M17.1398 10.8419C17.077 10.8132 17.0162 10.7776 16.9585 10.7348C16.6026 10.4708 16.308 10.1964 16.0646 9.91118C16.0338 9.455 16.3205 9.02217 16.7771 8.88635C17.0849 8.7948 17.3346 8.68583 17.5424 8.56059C17.6949 8.74952 17.8921 8.93714 18.1499 9.12841C18.5324 9.41212 18.6542 9.91688 18.4695 10.3349C18.1431 10.5197 17.772 10.677 17.3473 10.8033C17.2782 10.8239 17.209 10.8365 17.1398 10.8419Z" fill="#C9191E"/>
|
||||
<svg width="102" height="40" viewBox="0 0 102 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9631 8.43425L21.1133 9.34236C21.2075 9.35898 21.2622 9.45868 21.2247 9.54672C21.0649 9.92201 20.9527 10.1466 20.8496 10.3077C20.7632 10.4427 20.6969 10.5123 20.6273 10.5652C20.5543 10.6207 20.4167 10.7036 20.1389 10.7862C18.8283 11.176 18.0819 12.5544 18.4717 13.865C18.8616 15.1756 20.24 15.922 21.5506 15.5321C23.1965 15.0426 24.2747 14.1414 25.02 12.9768C25.3504 12.4606 25.5911 11.9308 25.7802 11.4869C25.8593 11.3012 25.9307 11.1276 25.9977 10.9649L25.9992 10.9612C26.1693 10.5474 26.6063 10.3109 27.0469 10.3886C28.1253 10.5788 28.884 10.9959 29.3228 11.64C29.7686 12.2854 29.8958 13.1508 29.7044 14.2362L26.7407 31.0438C26.5481 32.1362 26.1326 32.9059 25.4942 33.3529C24.8628 33.8011 24.0078 33.9302 22.9294 33.74L11.8457 31.7857C10.7672 31.5955 10.0045 31.1812 9.55746 30.5428C9.11741 29.9057 8.99369 29.0409 9.1863 27.9485L12.1499 11.1409C12.3413 10.0555 12.7534 9.28516 13.386 8.82996C14.0257 8.37599 14.8847 8.24409 15.9631 8.43425ZM17.0378 22.663C17.5098 23.337 18.148 23.7449 18.9526 23.8868C19.7572 24.0287 20.4965 23.8636 21.1704 23.3917C21.8444 22.9198 22.2524 22.2815 22.3942 21.4769C22.5361 20.6724 22.3711 19.9331 21.8991 19.2591C21.4272 18.5851 20.789 18.1772 19.9844 18.0353C19.1798 17.8934 18.4405 18.0585 17.7665 18.5304C17.0925 19.0023 16.6846 19.6406 16.5428 20.4452C16.4009 21.2497 16.5659 21.989 17.0378 22.663ZM12.1597 28.1942C12.0742 28.679 12.3979 29.1413 12.8826 29.2267L22.8301 30.9808C23.3149 31.0662 23.7772 30.7425 23.8626 30.2578L24.069 29.0875C24.1421 28.673 24.1026 28.2733 23.9505 27.8882C23.7984 27.5032 23.5618 27.188 23.2405 26.9428C22.5513 26.4317 21.8333 26.0128 21.0864 25.6863C20.3395 25.3598 19.5638 25.1256 18.7592 24.9837C17.9546 24.8418 17.1456 24.7966 16.332 24.848C15.5185 24.8994 14.7006 25.0474 13.8781 25.292C13.4923 25.4125 13.1621 25.6277 12.8876 25.9375C12.613 26.2473 12.4391 26.6095 12.366 27.024L12.1597 28.1942Z" fill="#2845C1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.4153 7.25835C28.4278 7.80513 27.9947 8.25852 27.4479 8.27102C27.1432 8.27799 26.6029 8.36208 26.0866 8.59358C25.5744 8.82325 25.1755 9.15497 24.9741 9.60658C24.8479 9.88939 24.744 10.142 24.6384 10.3989C24.5706 10.5637 24.5021 10.7302 24.4266 10.9076C24.2464 11.3306 24.0442 11.769 23.7819 12.1788C23.2235 13.0513 22.4351 13.7259 21.14 14.1111C20.6158 14.267 20.0644 13.9685 19.9085 13.4443C19.7526 12.92 20.0512 12.3687 20.5754 12.2127C21.4027 11.9666 21.8046 11.5941 22.1137 11.1112C22.2851 10.8433 22.4359 10.527 22.6044 10.1315C22.6602 10.0005 22.7214 9.85201 22.7866 9.69384C22.9027 9.41211 23.0316 9.0996 23.1652 8.79986C23.6291 7.75986 24.4873 7.14008 25.2763 6.78635C26.0611 6.43445 26.866 6.30323 27.4027 6.29096C27.9494 6.27845 28.4028 6.71157 28.4153 7.25835Z" fill="#C83F49"/>
|
||||
<path d="M16.8249 6.39789C16.3154 6.19914 16.0634 5.62496 16.2622 5.11543C16.461 4.6059 17.0351 4.35397 17.5447 4.55273C18.0448 4.74781 18.7562 5.14639 19.3734 5.74551C19.8305 6.18923 20.2735 6.78154 20.5072 7.51695C20.5399 7.6201 20.4498 7.71728 20.3432 7.69848L18.326 7.34279C18.1998 7.32055 18.0858 7.25585 17.9938 7.16661C17.5878 6.77248 17.1088 6.50865 16.8249 6.39789Z" fill="#C83F49"/>
|
||||
<path d="M20.9346 14.1493C20.8723 14.1209 20.8121 14.0856 20.755 14.0432C20.4026 13.7818 20.1108 13.5101 19.8697 13.2276C19.8392 12.7759 20.1232 12.3472 20.5754 12.2127C20.8802 12.1221 21.1274 12.0142 21.3332 11.8901C21.4842 12.0772 21.6795 12.263 21.9349 12.4524C22.3136 12.7334 22.4342 13.2333 22.2513 13.6472C21.9281 13.8303 21.5606 13.986 21.14 14.1111C21.0716 14.1315 21.003 14.144 20.9346 14.1493Z" fill="#C83F49"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.4198 15.5822C71.2645 15.5822 72.0007 15.7275 72.6302 16.0144C72.6566 16.0264 72.6853 16.0329 72.7143 16.0329H76.6529C76.7622 16.0329 76.8509 16.1216 76.8509 16.2309V17.9566C76.8509 18.066 76.7622 18.1547 76.6529 18.1547H74.9582C74.8185 18.1547 74.7224 18.2957 74.7635 18.4292C74.8753 18.793 74.9322 19.1752 74.9322 19.5763C74.9322 20.3132 74.7526 20.9905 74.3916 21.6071C74.0457 22.2238 73.5337 22.7122 72.8569 23.0732C72.1799 23.4343 71.3675 23.6148 70.4198 23.6148C69.9907 23.6148 69.5888 23.5759 69.2138 23.4993C69.1523 23.4868 69.0878 23.5023 69.0418 23.545C68.9855 23.5972 68.9329 23.6505 68.8851 23.7057C68.6899 23.9161 68.5922 24.1413 68.592 24.3817C68.592 24.6072 68.6599 24.7879 68.7951 24.9233C68.9305 25.0436 69.141 25.1041 69.4266 25.1041H72.4507C73.4284 25.1041 74.2409 25.2621 74.8877 25.578C75.5496 25.8788 76.0379 26.3004 76.3538 26.842C76.6848 27.3835 76.8509 28 76.8509 28.692C76.8509 29.3087 76.7073 29.8801 76.4215 30.4066C76.1508 30.9331 75.7527 31.3926 75.2262 31.7837C74.7149 32.1898 74.098 32.4977 73.3762 32.7083C72.6541 32.9339 71.8561 33.0467 70.9836 33.0467C69.8104 33.0467 68.7947 32.9041 67.9373 32.6183C67.0951 32.3476 66.4404 31.9413 65.9742 31.3998C65.523 30.8584 65.2973 30.1964 65.2972 29.4144C65.2972 28.7074 65.5085 28.0827 65.9297 27.5412C66.1454 27.2715 66.4063 27.0132 66.7127 26.7671C66.8097 26.6892 66.8169 26.5428 66.7339 26.45C66.6244 26.3276 66.5289 26.195 66.448 26.0518C66.2676 25.7059 66.1772 25.3447 66.1772 24.9687C66.1773 24.3821 66.3578 23.863 66.7188 23.4117C66.8658 23.2294 67.0444 23.057 67.2537 22.8934C67.3604 22.8101 67.3642 22.6465 67.2619 22.5578C66.9468 22.2847 66.6828 21.9684 66.4712 21.6071C66.1103 20.9905 65.9297 20.3132 65.9297 19.5763C65.9297 18.8542 66.1102 18.1916 66.4712 17.5899C66.8322 16.9734 67.3434 16.4847 68.005 16.1238C68.682 15.7628 69.4872 15.5822 70.4198 15.5822ZM68.7065 27.2481C68.6601 27.2481 68.6149 27.2644 68.5798 27.2947C68.3541 27.4896 68.1547 27.6995 67.9828 27.9251C67.7572 28.256 67.6443 28.647 67.6443 29.0982C67.6443 29.4893 67.7725 29.8278 68.0282 30.1136C68.2989 30.4143 68.6747 30.6401 69.1558 30.7905C69.6523 30.956 70.2544 31.0391 70.9614 31.0391C71.6985 31.0391 72.3232 30.9488 72.8346 30.7683C73.3459 30.6028 73.7298 30.3545 73.9855 30.0236C74.2562 29.7078 74.3916 29.3385 74.3916 28.9173C74.3916 28.6017 74.3237 28.316 74.1885 28.0605C74.0532 27.8199 73.8273 27.6239 73.5116 27.4735C73.1957 27.323 72.7587 27.2481 72.2022 27.2481H68.7065ZM70.4421 17.4545C69.9759 17.4545 69.5695 17.553 69.2235 17.7485C68.8928 17.929 68.6371 18.1774 68.4566 18.4931C68.2913 18.809 68.2081 19.1702 68.2081 19.5763C68.2082 19.9823 68.2912 20.3508 68.4566 20.6816C68.6371 21.0125 68.8928 21.268 69.2235 21.4485C69.5695 21.629 69.9759 21.7193 70.4421 21.7193C70.9084 21.7193 71.3074 21.6291 71.6383 21.4485C71.9692 21.253 72.2176 20.9975 72.383 20.6816C72.5635 20.3508 72.6537 19.9823 72.6538 19.5763C72.6538 19.1551 72.5635 18.7858 72.383 18.4699C72.2175 18.1541 71.9692 17.9057 71.6383 17.7253C71.3074 17.5448 70.9084 17.4545 70.4421 17.4545Z" fill="#2845C1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.0663 15.5822C59.8483 15.5822 60.5554 15.7096 61.1871 15.9652C61.8188 16.2209 62.3606 16.5749 62.8118 17.0261C63.2781 17.4774 63.632 18.0118 63.8727 18.6285C64.1133 19.2451 64.2334 19.9143 64.2334 20.6362C64.2334 20.907 64.219 21.1706 64.1889 21.4263C64.1671 21.6115 64.1412 21.773 64.1114 21.9108C64.0925 21.9981 64.0138 22.0578 63.9245 22.0578H56.0046C55.8896 22.0578 55.7984 22.1555 55.8105 22.2698C55.8739 22.868 56.0185 23.384 56.2463 23.8179C56.5621 24.4195 57.0055 24.8709 57.577 25.1718C58.1486 25.4727 58.7959 25.6234 59.5179 25.6234C60.1948 25.6234 60.8042 25.488 61.3457 25.2173C61.8337 24.9798 62.2348 24.6727 62.5493 24.2964C62.6214 24.2101 62.7497 24.1925 62.8376 24.2626L64.3098 25.4363C64.3934 25.5029 64.4093 25.624 64.3426 25.7074C63.8329 26.3439 63.1869 26.8575 62.4056 27.2481C61.5633 27.6541 60.608 27.8574 59.5402 27.8574C58.2165 27.8574 57.088 27.5784 56.1554 27.0218C55.2229 26.4502 54.5087 25.6982 54.0123 24.7656C53.5159 23.833 53.2677 22.8175 53.2677 21.7193C53.2677 20.862 53.395 20.0647 53.6506 19.3277C53.9214 18.5906 54.305 17.9434 54.8015 17.3868C55.2978 16.8153 55.9073 16.372 56.6292 16.0561C57.3513 15.7402 58.1638 15.5822 59.0663 15.5822ZM58.9986 17.6353C58.3369 17.6354 57.758 17.7862 57.2617 18.087C56.7804 18.3878 56.4111 18.8311 56.1554 19.4177C56.0726 19.6028 56.0045 19.8025 55.9492 20.0163C55.9178 20.1378 56.012 20.2532 56.1375 20.2532H61.5891C61.7009 20.2532 61.7913 20.1606 61.7796 20.0493C61.7421 19.6925 61.6355 19.3543 61.4579 19.0347C61.2473 18.6136 60.9394 18.275 60.5334 18.0193C60.1272 17.7635 59.6154 17.6353 58.9986 17.6353Z" fill="#2845C1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M88.7228 15.5822C89.5048 15.5822 90.2119 15.7096 90.8436 15.9652C91.4753 16.2209 92.0171 16.5749 92.4683 17.0261C92.9346 17.4774 93.2885 18.0118 93.5292 18.6285C93.7698 19.2451 93.8899 19.9143 93.8899 20.6362C93.8899 20.907 93.8755 21.1706 93.8454 21.4263C93.8236 21.6115 93.7977 21.773 93.7679 21.9108C93.7491 21.9981 93.6703 22.0578 93.581 22.0578H85.6611C85.5461 22.0578 85.455 22.1555 85.4671 22.2698C85.5304 22.868 85.675 23.384 85.9028 23.8179C86.2187 24.4195 86.662 24.8709 87.2335 25.1718C87.8051 25.4727 88.4524 25.6234 89.1744 25.6234C89.8513 25.6234 90.4607 25.488 91.0022 25.2173C91.4902 24.9798 91.8913 24.6727 92.2058 24.2964C92.2779 24.2101 92.4062 24.1925 92.4941 24.2626L93.9664 25.4363C94.0499 25.5029 94.0659 25.624 93.9991 25.7074C93.4894 26.3439 92.8434 26.8575 92.0621 27.2481C91.2198 27.6542 90.2646 27.8574 89.1967 27.8574C87.8729 27.8574 86.7445 27.5784 85.8119 27.0218C84.8794 26.4502 84.1652 25.6982 83.6688 24.7656C83.1724 23.833 82.9242 22.8175 82.9242 21.7193C82.9242 20.862 83.0515 20.0647 83.3071 19.3277C83.5779 18.5906 83.9616 17.9434 84.458 17.3868C84.9543 16.8153 85.5638 16.372 86.2858 16.0561C87.0078 15.7402 87.8203 15.5822 88.7228 15.5822ZM88.6551 17.6353C87.9934 17.6354 87.4146 17.7862 86.9182 18.087C86.4369 18.3878 86.0676 18.8311 85.8119 19.4177C85.7291 19.6028 85.661 19.8025 85.6057 20.0163C85.5743 20.1378 85.6685 20.2532 85.794 20.2532H91.2456C91.3574 20.2532 91.4478 20.1606 91.4361 20.0493C91.3986 19.6925 91.292 19.3543 91.1144 19.0347C90.9038 18.6136 90.5959 18.275 90.1899 18.0193C89.7837 17.7635 89.2719 17.6353 88.6551 17.6353Z" fill="#2845C1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.1829 11.6104C46.8676 11.6105 48.191 12.0248 49.1537 12.8522C50.1314 13.6795 50.6207 14.7998 50.6208 16.2137C50.6208 17.1613 50.395 17.9811 49.9438 18.673C49.5076 19.35 48.8829 19.8772 48.0706 20.2532C48.045 20.2648 48.0353 20.295 48.0514 20.318L52.7942 27.0941C52.8861 27.2254 52.7922 27.4058 52.632 27.4058H49.8695C49.803 27.4058 49.7409 27.3723 49.7043 27.3168L45.4859 20.9267C45.4297 20.8416 45.3338 20.7925 45.2318 20.7943C45.2155 20.7946 45.1992 20.7948 45.1829 20.7948H43.5754C43.466 20.7948 43.3773 20.8835 43.3773 20.9928V27.2077C43.3773 27.3171 43.2887 27.4058 43.1793 27.4058H40.7999C40.6905 27.4058 40.6018 27.3171 40.6018 27.2077V11.8085C40.6018 11.6991 40.6905 11.6104 40.7999 11.6104H45.1829ZM43.3773 18.2951C43.3773 18.4045 43.466 18.4931 43.5754 18.4931H45.3183C46.1002 18.4931 46.7017 18.2898 47.1228 17.8839C47.544 17.4627 47.7553 16.8903 47.7553 16.1683C47.7552 15.4915 47.544 14.9497 47.1228 14.5436C46.7017 14.1377 46.1001 13.9353 45.3183 13.9353H43.5754C43.466 13.9353 43.3773 14.024 43.3773 14.1333V18.2951Z" fill="#2845C1"/>
|
||||
<path d="M80.7927 27.2077C80.7927 27.3171 80.7041 27.4058 80.5947 27.4058H78.5093C78.3999 27.4058 78.3112 27.3171 78.3112 27.2077V16.2309C78.3112 16.1216 78.3999 16.0329 78.5093 16.0329H80.5947C80.7041 16.0329 80.7927 16.1216 80.7927 16.2309V27.2077Z" fill="#2845C1"/>
|
||||
<path d="M60.4572 14.1456C60.42 14.1975 60.3601 14.2283 60.2962 14.2283H58.3846C58.2293 14.2283 58.1344 14.0578 58.2163 13.9258L60.2944 10.5764C60.3305 10.5182 60.3942 10.4828 60.4627 10.4828H62.6969C62.8582 10.4828 62.9519 10.6652 62.8579 10.7962L60.4572 14.1456Z" fill="#2845C1"/>
|
||||
<path d="M79.5742 10.3474C79.875 10.3474 80.1459 10.4224 80.3866 10.5727C80.6423 10.7232 80.8382 10.9263 80.9736 11.182C81.124 11.4227 81.1989 11.6935 81.1989 11.9944C81.1989 12.2951 81.1239 12.5733 80.9736 12.8289C80.8382 13.0847 80.6423 13.2878 80.3866 13.4382C80.1459 13.5736 79.875 13.6413 79.5742 13.6413C79.2734 13.6413 78.9953 13.5736 78.7396 13.4382C78.4839 13.2878 78.2808 13.0847 78.1304 12.8289C77.98 12.5733 77.905 12.2951 77.905 11.9944C77.905 11.5431 78.063 11.1594 78.3789 10.8435C78.7098 10.5127 79.1081 10.3475 79.5742 10.3474Z" fill="#2845C1"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,4 @@
|
||||
import { ComponentPropsWithRef } from 'react';
|
||||
import { ComponentPropsWithRef, HTMLElementType } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { CSSProperties, RuleSet } from 'styled-components/dist/types';
|
||||
|
||||
@@ -11,11 +11,13 @@ import {
|
||||
import { hideEffect, showEffect } from './Effect';
|
||||
|
||||
export interface BoxProps {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
as?: HTMLElementType;
|
||||
$align?: CSSProperties['alignItems'];
|
||||
$background?: CSSProperties['background'];
|
||||
$border?: CSSProperties['border'];
|
||||
$color?: CSSProperties['color'];
|
||||
$css?: string | RuleSet<object>;
|
||||
$cursor?: CSSProperties['cursor'];
|
||||
$direction?: CSSProperties['flexDirection'];
|
||||
$display?: CSSProperties['display'];
|
||||
$effect?: 'show' | 'hide';
|
||||
@@ -24,6 +26,7 @@ export interface BoxProps {
|
||||
$hasTransition?: boolean | 'slow';
|
||||
$height?: CSSProperties['height'];
|
||||
$justify?: CSSProperties['justifyContent'];
|
||||
$opacity?: CSSProperties['opacity'];
|
||||
$overflow?: CSSProperties['overflow'];
|
||||
$margin?: MarginPadding;
|
||||
$maxHeight?: CSSProperties['maxHeight'];
|
||||
@@ -36,28 +39,44 @@ export interface BoxProps {
|
||||
$shrink?: CSSProperties['flexShrink'];
|
||||
$transition?: CSSProperties['transition'];
|
||||
$width?: CSSProperties['width'];
|
||||
$wrap?: CSSProperties['flexWrap'];
|
||||
$zIndex?: CSSProperties['zIndex'];
|
||||
$wrap?: CSSProperties['flexWrap'];
|
||||
// Theming props
|
||||
$layer?: 'background' | 'content' | 'border';
|
||||
$theme?:
|
||||
| 'brand'
|
||||
| 'error'
|
||||
| 'gray'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'neutral'
|
||||
| 'contextual'
|
||||
| 'disabled'
|
||||
| (string & {});
|
||||
$scope?: 'surface' | 'semantic' | 'palette' | (string & {});
|
||||
$variation?: 'primary' | 'secondary' | 'tertiary' | (string & {});
|
||||
$withThemeBG?: boolean;
|
||||
$withThemeBorder?: boolean;
|
||||
$withThemeInherited?: boolean;
|
||||
}
|
||||
|
||||
export type BoxType = ComponentPropsWithRef<typeof Box>;
|
||||
|
||||
export const Box = styled('div')<BoxProps>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
${({ $align }) => $align && `align-items: ${$align};`}
|
||||
${({ $background }) => $background && `background: ${$background};`}
|
||||
${({ $color }) => $color && `color: ${$color};`}
|
||||
${({ $direction }) => $direction && `flex-direction: ${$direction};`}
|
||||
${({ $display }) => $display && `display: ${$display};`}
|
||||
${({ $cursor }) => $cursor && `cursor: ${$cursor};`}
|
||||
${({ $direction }) => `flex-direction: ${$direction || 'column'};`}
|
||||
${({ $display, as }) =>
|
||||
`display: ${$display || (as?.match('span|input') ? 'inline-flex' : 'flex')};`}
|
||||
${({ $flex }) => $flex && `flex: ${$flex};`}
|
||||
${({ $gap }) => $gap && `gap: ${$gap};`}
|
||||
${({ $height }) => $height && `height: ${$height};`}
|
||||
${({ $hasTransition }) =>
|
||||
$hasTransition && $hasTransition === 'slow'
|
||||
? `transition: all 0.5s ease-in-out;`
|
||||
? `transition: all 0.5s var(--c--globals--transitions--ease-out);`
|
||||
: $hasTransition
|
||||
? `transition: all 0.3s ease-in-out;`
|
||||
? `transition: all var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out);`
|
||||
: ''}
|
||||
${({ $justify }) => $justify && `justify-content: ${$justify};`}
|
||||
${({ $margin }) => $margin && stylesMargin($margin)}
|
||||
@@ -65,11 +84,92 @@ export const Box = styled('div')<BoxProps>`
|
||||
${({ $minHeight }) => $minHeight && `min-height: ${$minHeight};`}
|
||||
${({ $maxWidth }) => $maxWidth && `max-width: ${$maxWidth};`}
|
||||
${({ $minWidth }) => $minWidth && `min-width: ${$minWidth};`}
|
||||
${({ $opacity }) => $opacity && `opacity: ${$opacity};`}
|
||||
${({ $overflow }) => $overflow && `overflow: ${$overflow};`}
|
||||
${({ $padding }) => $padding && stylesPadding($padding)}
|
||||
${({ $position }) => $position && `position: ${$position};`}
|
||||
${({ $radius }) => $radius && `border-radius: ${$radius};`}
|
||||
${({ $shrink }) => $shrink && `flex-shrink: ${$shrink};`}
|
||||
${({
|
||||
$layer = 'border',
|
||||
$theme = 'brand',
|
||||
$variation = 'primary',
|
||||
$scope = 'semantic',
|
||||
$border,
|
||||
$withThemeBorder,
|
||||
$withThemeInherited,
|
||||
}) => {
|
||||
if ($border) {
|
||||
return `border: ${$border};`;
|
||||
}
|
||||
|
||||
if (!$layer || !$scope || !$theme || !$withThemeBorder) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($withThemeInherited) {
|
||||
return `border: inherit;`;
|
||||
}
|
||||
|
||||
return `border: 1px solid var(--c--contextuals--${$layer}--${$scope}${$theme ? `--${$theme}` : ''}${$variation ? `--${$variation}` : ''});`;
|
||||
}}
|
||||
${({
|
||||
$layer = 'background',
|
||||
$theme = 'brand',
|
||||
$variation = 'primary',
|
||||
$scope = 'semantic',
|
||||
$background,
|
||||
$withThemeBG,
|
||||
$withThemeInherited,
|
||||
}) => {
|
||||
if ($background) {
|
||||
return `background: ${$background};`;
|
||||
}
|
||||
|
||||
if (!$layer || !$scope || !$theme || !$withThemeBG) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($withThemeInherited) {
|
||||
return `background: inherit;`;
|
||||
}
|
||||
|
||||
return `background: var(--c--contextuals--${$layer}--${$scope}${$theme ? `--${$theme}` : ''}${$variation ? `--${$variation}` : ''});`;
|
||||
}}
|
||||
${({
|
||||
$layer = 'content',
|
||||
$theme = 'neutral',
|
||||
$variation = 'primary',
|
||||
$scope = 'semantic',
|
||||
$color,
|
||||
$withThemeBG,
|
||||
$withThemeInherited,
|
||||
}) => {
|
||||
if ($color) {
|
||||
return `color: ${$color};`;
|
||||
}
|
||||
|
||||
if (!$layer || !$scope) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// There is a special case when primary with background
|
||||
if (
|
||||
$withThemeBG &&
|
||||
$layer === 'content' &&
|
||||
$scope === 'semantic' &&
|
||||
$variation === 'primary' &&
|
||||
$theme
|
||||
) {
|
||||
$variation = `on-${$theme}`;
|
||||
}
|
||||
|
||||
if ($withThemeInherited) {
|
||||
return `color: inherit;`;
|
||||
}
|
||||
|
||||
return `color: var(--c--contextuals--${$layer}--${$scope}${$theme ? `--${$theme}` : ''}${$variation ? `--${$variation}` : ''});`;
|
||||
}}
|
||||
${({ $transition }) => $transition && `transition: ${$transition};`}
|
||||
${({ $width }) => $width && `width: ${$width};`}
|
||||
${({ $wrap }) => $wrap && `flex-wrap: ${$wrap};`}
|
||||
@@ -89,7 +189,7 @@ export const Box = styled('div')<BoxProps>`
|
||||
return (
|
||||
effect &&
|
||||
`
|
||||
transition: all 0.3s ease-in-out;
|
||||
transition: all var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out);
|
||||
${effect}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ const BoxButton = forwardRef<HTMLDivElement, BoxButtonType>(
|
||||
font-family: inherit;
|
||||
|
||||
color: ${props.disabled
|
||||
? 'var(--c--theme--colors--greyscale-400) !important'
|
||||
? 'var(--c--theme--colors--gray-400) !important'
|
||||
: 'inherit'};
|
||||
${$css || ''}
|
||||
`}
|
||||
|
||||
@@ -12,7 +12,7 @@ export const Card = ({ children, ...props }: PropsWithChildren<BoxType>) => {
|
||||
$background="white"
|
||||
$radius="4px"
|
||||
$css={`
|
||||
border: 1px solid ${colorsTokens()['greyscale-050']};
|
||||
border: 1px solid ${colorsTokens()['gray-050']};
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -40,6 +40,8 @@ interface DropButtonProps {
|
||||
button: ReactNode;
|
||||
isOpen?: boolean;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
/** Accessible name for the trigger button (recommended for screen readers). */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export const DropButton = ({
|
||||
@@ -47,6 +49,7 @@ export const DropButton = ({
|
||||
isOpen = false,
|
||||
onOpenChange,
|
||||
children,
|
||||
ariaLabel,
|
||||
}: PropsWithChildren<DropButtonProps>) => {
|
||||
const [opacity, setOpacity] = useState(false);
|
||||
const [isLocalOpen, setIsLocalOpen] = useState(isOpen);
|
||||
@@ -75,7 +78,7 @@ export const DropButton = ({
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<DialogTrigger onOpenChange={onOpenChangeHandler} isOpen={isLocalOpen}>
|
||||
<StyledButton>{button}</StyledButton>
|
||||
<StyledButton aria-label={ariaLabel}>{button}</StyledButton>
|
||||
<StyledPopover
|
||||
style={{ opacity: opacity ? 1 : 0 }}
|
||||
isOpen={isLocalOpen}
|
||||
|
||||
@@ -1,41 +1,17 @@
|
||||
import clsx from 'clsx';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Text, TextType } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
type IconProps = TextType & {
|
||||
iconName: string;
|
||||
};
|
||||
export const Icon = ({ iconName, ...textProps }: IconProps) => {
|
||||
return (
|
||||
<Text $isMaterialIcon {...textProps}>
|
||||
{iconName}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
interface IconBGProps extends TextType {
|
||||
iconName: string;
|
||||
}
|
||||
|
||||
export const IconBG = ({ iconName, ...textProps }: IconBGProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$size="36px"
|
||||
$theme="primary"
|
||||
$variation="600"
|
||||
$background={colorsTokens()['primary-050']}
|
||||
$css={`
|
||||
border: 1px solid ${colorsTokens()['primary-200']};
|
||||
user-select: none;
|
||||
`}
|
||||
$radius="12px"
|
||||
$padding="4px"
|
||||
$margin="auto"
|
||||
{...textProps}
|
||||
aria-hidden="true"
|
||||
className={clsx('material-icons', textProps.className)}
|
||||
>
|
||||
{iconName}
|
||||
</Text>
|
||||
@@ -48,16 +24,15 @@ type IconOptionsProps = TextType & {
|
||||
|
||||
export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => {
|
||||
return (
|
||||
<Text
|
||||
<Icon
|
||||
{...props}
|
||||
$isMaterialIcon
|
||||
$theme="primary"
|
||||
aria-hidden="true"
|
||||
iconName={isHorizontal ? 'more_horiz' : 'more_vert'}
|
||||
$css={css`
|
||||
color: var(--c--contextuals--content--semantic--brand--tertiary);
|
||||
user-select: none;
|
||||
${props.$css}
|
||||
`}
|
||||
>
|
||||
{isHorizontal ? 'more_horiz' : 'more_vert'}
|
||||
</Text>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,32 +12,34 @@ export const Input = ({ label, error, required, ...props }: InputProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Box $display="flex" $gap="4px">
|
||||
<label
|
||||
htmlFor={label}
|
||||
style={{ fontWeight: 500, color: colorsTokens()['greyscale-900'] }}
|
||||
>
|
||||
{label} {required && '*'}
|
||||
</label>
|
||||
<Box>
|
||||
<Box $display="flex" $gap="8px" $margin={{ bottom: 'sm' }}>
|
||||
<label
|
||||
htmlFor={label}
|
||||
style={{ fontWeight: 500, color: colorsTokens()['gray-900'] }}
|
||||
>
|
||||
{label} {required && '*'}
|
||||
</label>
|
||||
<input
|
||||
id={label}
|
||||
aria-required={required}
|
||||
required={required}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px',
|
||||
border: `1px solid ${error ? colorsTokens()['error-500'] : 'var(--c--contextuals--border--semantic--neutral--tertiary)'}`,
|
||||
background: colorsTokens()['gray-000'],
|
||||
color: colorsTokens()['gray-550'],
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
{error && (
|
||||
<Text $size="xs" $theme="danger" $variation="600">
|
||||
<Text $size="xs" $theme="error" $variation="secondary">
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
<input
|
||||
id={label}
|
||||
aria-required={required}
|
||||
required={required}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px',
|
||||
border: `1px solid ${error ? colorsTokens()['danger-500'] : colorsTokens()['greyscale-400']}`,
|
||||
background: colorsTokens()['greyscale-050'],
|
||||
color: colorsTokens()['greyscale-900'],
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
export type LocalInputProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||
label: string;
|
||||
right: React.ReactNode;
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const LocalInput = ({
|
||||
label,
|
||||
right,
|
||||
error,
|
||||
required,
|
||||
id,
|
||||
...inputProps
|
||||
}: LocalInputProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const borderColor = error
|
||||
? colorsTokens()['error-500']
|
||||
: 'var(--c--contextuals--border--semantic--neutral--tertiary)';
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box $margin={{ bottom: 'sm' }}>
|
||||
<label
|
||||
htmlFor={id}
|
||||
style={{ fontWeight: 500, color: colorsTokens()['gray-900'] }}
|
||||
>
|
||||
{label} {required && '*'}
|
||||
</label>
|
||||
</Box>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'stretch',
|
||||
border: `1px solid ${borderColor}`,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
background: colorsTokens()['gray-000'],
|
||||
}}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
aria-required={required}
|
||||
required={required}
|
||||
style={{
|
||||
flex: '1 1 0',
|
||||
minWidth: 0,
|
||||
padding: '12px',
|
||||
border: 'none',
|
||||
borderRadius: 0,
|
||||
fontSize: 14,
|
||||
background: 'transparent',
|
||||
color: colorsTokens()['gray-550'],
|
||||
outline: 'none',
|
||||
}}
|
||||
{...inputProps}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
background: 'transparent',
|
||||
}}
|
||||
>
|
||||
{right}
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
<Text
|
||||
$size="xs"
|
||||
$theme="error"
|
||||
$variation="secondary"
|
||||
$margin={{ top: '2xs' }}
|
||||
>
|
||||
{error}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Modal as CunninghamModal,
|
||||
ModalProps,
|
||||
} from '@openfun/cunningham-react';
|
||||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
// Define a wrapper component that extends ModalProps to accept the same props as the Modal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Tooltip } from '@openfun/cunningham-react';
|
||||
import { Tooltip } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { Box, Icon } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
interface TagContentProps {
|
||||
@@ -9,41 +9,68 @@ interface TagContentProps {
|
||||
showTooltip?: boolean;
|
||||
tooltipType?: 'domain' | 'mail';
|
||||
placement?: 'top' | 'bottom';
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
const TagContent = ({ status }: TagContentProps) => {
|
||||
const TagContent = ({ status, showIcon }: TagContentProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const translatedStatus = {
|
||||
pending: t('pending'),
|
||||
enabled: t('enabled'),
|
||||
disabled: t('disabled'),
|
||||
action_required: t('action required'),
|
||||
failed: t('failed'),
|
||||
};
|
||||
|
||||
const textColor = {
|
||||
pending: colorsTokens()['info-600'],
|
||||
enabled: colorsTokens()['success-600'],
|
||||
disabled: colorsTokens()['greyscale-600'],
|
||||
disabled: colorsTokens()['gray-600'],
|
||||
action_required: colorsTokens()['warning-600'],
|
||||
failed: colorsTokens()['danger-600'],
|
||||
failed: colorsTokens()['error-600'],
|
||||
};
|
||||
|
||||
const backgroundColor = {
|
||||
pending: colorsTokens()['info-100'],
|
||||
enabled: colorsTokens()['success-100'],
|
||||
disabled: colorsTokens()['greyscale-100'],
|
||||
disabled: colorsTokens()['gray-100'],
|
||||
action_required: colorsTokens()['warning-100'],
|
||||
failed: colorsTokens()['danger-100'],
|
||||
failed: colorsTokens()['error-100'],
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
$display="flex"
|
||||
$direction="row"
|
||||
$gap="4px"
|
||||
$background={backgroundColor[status]}
|
||||
$color={textColor[status]}
|
||||
$radius="4px"
|
||||
$css={`
|
||||
padding: 4px 8px;
|
||||
font-weight: 600;
|
||||
cursor: default;
|
||||
text-transform: capitalize;
|
||||
`}
|
||||
>
|
||||
{t(status).replace('_', ' ')}
|
||||
{translatedStatus[status]}
|
||||
{showIcon &&
|
||||
(status === 'enabled' ? (
|
||||
<Icon
|
||||
$size="small"
|
||||
$weight="bold"
|
||||
iconName="check"
|
||||
$color="inherit"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
$size="small"
|
||||
$weight="bold"
|
||||
iconName="arrow_forward"
|
||||
$color="inherit"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -80,10 +107,10 @@ export const Tag = ({ ...props }: TagContentProps) => {
|
||||
return props.showTooltip ? (
|
||||
<Tooltip content={tooltipContent} placement={props.placement || 'top'}>
|
||||
<Box>
|
||||
<TagContent status={props.status} />
|
||||
<TagContent status={props.status} showIcon={props.showIcon} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<TagContent status={props.status} />
|
||||
<TagContent status={props.status} showIcon={props.showIcon} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,70 +5,36 @@ import { tokens } from '@/cunningham';
|
||||
|
||||
import { Box, BoxProps } from './Box';
|
||||
|
||||
const { sizes } = tokens.themes.default.theme.font;
|
||||
const { sizes } = tokens.themes.default.globals.font;
|
||||
type TextSizes = keyof typeof sizes;
|
||||
|
||||
export interface TextProps extends BoxProps {
|
||||
as?: keyof Pick<
|
||||
HTMLElementTagNameMap,
|
||||
'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||
>;
|
||||
$elipsis?: boolean;
|
||||
$isMaterialIcon?: boolean;
|
||||
as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
$ellipsis?: boolean;
|
||||
$weight?: CSSProperties['fontWeight'];
|
||||
$textAlign?: CSSProperties['textAlign'];
|
||||
$textTransform?: CSSProperties['textTransform'];
|
||||
$size?: TextSizes | (string & {});
|
||||
$theme?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'greyscale';
|
||||
$variation?:
|
||||
| 'text'
|
||||
| '000'
|
||||
| '100'
|
||||
| '200'
|
||||
| '300'
|
||||
| '400'
|
||||
| '500'
|
||||
| '600'
|
||||
| '700'
|
||||
| '800'
|
||||
| '900'
|
||||
| '1000';
|
||||
}
|
||||
|
||||
export type TextType = ComponentPropsWithRef<typeof Text>;
|
||||
|
||||
export const TextStyled = styled(Box)<TextProps>`
|
||||
${({ $textAlign }) => $textAlign && `text-align: ${$textAlign};`}
|
||||
${({ $textTransform }) =>
|
||||
$textTransform && `text-transform: ${$textTransform};`}
|
||||
${({ $weight }) => $weight && `font-weight: ${$weight};`}
|
||||
${({ $size }) =>
|
||||
$size &&
|
||||
`font-size: ${$size in sizes ? sizes[$size as TextSizes] : $size};`}
|
||||
${({ $theme, $variation }) =>
|
||||
`color: var(--c--theme--colors--${$theme}-${$variation});`}
|
||||
${({ $color }) => $color && `color: ${$color};`}
|
||||
${({ $elipsis }) =>
|
||||
$elipsis &&
|
||||
${({ $ellipsis }) =>
|
||||
$ellipsis &&
|
||||
`white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`}
|
||||
`;
|
||||
|
||||
const Text = forwardRef<HTMLElement, ComponentPropsWithRef<typeof TextStyled>>(
|
||||
({ className, $isMaterialIcon, ...props }, ref) => {
|
||||
return (
|
||||
<TextStyled
|
||||
ref={ref}
|
||||
as="span"
|
||||
$theme="greyscale"
|
||||
$variation="text"
|
||||
className={`${className || ''}${$isMaterialIcon ? ' material-icons' : ''}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
(props, ref) => {
|
||||
return <TextStyled ref={ref} as="span" {...props} />;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export * from './Effect';
|
||||
export * from './DropButton';
|
||||
export * from './Icon';
|
||||
export * from './Input';
|
||||
export * from './LocalInput';
|
||||
export * from './Link';
|
||||
export * from './LogoGouv';
|
||||
export * from './Tag';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Modal as CunninghamModal,
|
||||
ModalProps,
|
||||
} from '@openfun/cunningham-react';
|
||||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { HorizontalSeparator } from '@/components';
|
||||
@@ -33,6 +33,8 @@ export const CustomModal: React.FC<
|
||||
<HorizontalSeparator $withPadding={true}></HorizontalSeparator>
|
||||
<div>
|
||||
{children}
|
||||
<HorizontalSeparator $withPadding={true}></HorizontalSeparator>
|
||||
|
||||
{/* modal content */}
|
||||
{/*<div className={style.content}>{children}</div>*/}
|
||||
|
||||
|
||||
@@ -23,11 +23,16 @@
|
||||
border: none;
|
||||
font-size: 34px;
|
||||
cursor: pointer;
|
||||
color: var(--c--theme--colors--greyscale-700);
|
||||
color: var(--c--theme--colors--gray-700);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--c--theme--spacings--md);
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.modalCustom .c__modal__content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.c__modal__footer {
|
||||
@@ -42,7 +47,7 @@
|
||||
padding: 0 var(--c--theme--spacings--md);
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--c--theme--colors--greyscale-200);
|
||||
border-top: 1px solid var(--c--theme--colors--gray-200);
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
@@ -63,9 +68,9 @@
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
border-radius: 4px;
|
||||
background-color: var(--c--theme--colors--primary-200);
|
||||
background-color: var(--c--theme--colors--brand-200);
|
||||
&.active {
|
||||
background-color: var(--c--theme--colors--primary-800);
|
||||
background-color: var(--c--theme--colors--brand-800);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const HorizontalSeparator = ({
|
||||
$background={
|
||||
variant === SeparatorVariant.DARK
|
||||
? '#e5e5e533'
|
||||
: colorsTokens()['greyscale-100']
|
||||
: colorsTokens()['gray-100']
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { Box } from '../Box';
|
||||
|
||||
type Props = {
|
||||
@@ -13,15 +11,14 @@ export const SeparatedSection = ({
|
||||
showSeparator = true,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
const theme = useCunninghamTheme();
|
||||
const colors = theme.colorsTokens();
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
width: 100%;
|
||||
${showSeparator &&
|
||||
css`
|
||||
border-bottom: 1px solid ${colors?.['greyscale-200']};
|
||||
border-bottom: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -1,54 +1,109 @@
|
||||
import * as React from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import clsx from 'clsx';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { Icon } from '@/components';
|
||||
|
||||
import style from './custom-tabs.module.scss';
|
||||
|
||||
type TabsOption = {
|
||||
ariaLabel?: string;
|
||||
export type TabData = {
|
||||
id: string;
|
||||
label: string;
|
||||
iconName?: string;
|
||||
id?: string;
|
||||
icon?: ReactNode;
|
||||
subtext?: string;
|
||||
content: ReactNode;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tabs: TabsOption[];
|
||||
export type TabsProps = {
|
||||
theme?: 'brand' | 'neutral';
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
tabs: TabData[];
|
||||
defaultSelectedTab?: string;
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export const CustomTabs = ({ tabs }: Props) => {
|
||||
export const CustomTabs = ({
|
||||
orientation = 'horizontal',
|
||||
theme = 'brand',
|
||||
tabs,
|
||||
defaultSelectedTab,
|
||||
fullWidth = false,
|
||||
}: TabsProps) => {
|
||||
const [selectedTabId, setSelectedTabId] = useState(
|
||||
defaultSelectedTab ?? tabs[0]?.id,
|
||||
);
|
||||
|
||||
const activeTab =
|
||||
tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0] ?? null;
|
||||
|
||||
if (tabs.length === 0 || !activeTab) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={style.customTabsContainer}>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<div
|
||||
className={clsx(style.root, {
|
||||
[style.fullWidth]: fullWidth,
|
||||
[style.neutral]: theme === 'neutral',
|
||||
[style.orientationVertical]: orientation === 'vertical',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
width: 'auto',
|
||||
flexDirection: orientation === 'horizontal' ? 'row' : 'column',
|
||||
alignItems: 'center',
|
||||
borderRadius: 8,
|
||||
backgroundColor:
|
||||
'var(--c--contextuals--background--surface--primary)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid var(--c--contextuals--border--surface--primary)',
|
||||
padding: 4,
|
||||
boxShadow: 'rgba(0, 0, 0, 0.05) 0px 2px 4px 0px',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab) => {
|
||||
const id = tab.id ?? tab.label;
|
||||
const isSelected = tab.id === activeTab?.id;
|
||||
return (
|
||||
<Tab key={id} aria-label={tab.ariaLabel} id={id}>
|
||||
<Box $direction="row" $gap="5px">
|
||||
{tab.iconName && (
|
||||
<span className="material-icons" aria-hidden="true">
|
||||
{tab.iconName}
|
||||
<Button
|
||||
key={tab.id}
|
||||
onClick={() => setSelectedTabId(tab.id)}
|
||||
variant={isSelected ? 'secondary' : 'tertiary'}
|
||||
color={isSelected ? 'brand' : 'neutral'}
|
||||
size="small"
|
||||
aria-pressed={isSelected}
|
||||
icon={
|
||||
<Icon
|
||||
iconName={tab.iconName ?? ''}
|
||||
$color={isSelected ? 'brand' : 'neutral'}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{tab.icon && !tab.iconName && tab.icon}
|
||||
<span>
|
||||
<span className="react-aria-Tab__title">{tab.label}</span>
|
||||
{tab.subtext && orientation === 'vertical' && (
|
||||
<span className="react-aria-Tab__subtext">
|
||||
{tab.subtext}
|
||||
</span>
|
||||
)}
|
||||
{tab.label}
|
||||
</Box>
|
||||
</Tab>
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
|
||||
{tabs.map((tab) => {
|
||||
const id = tab.id ?? tab.label;
|
||||
return (
|
||||
<TabPanel key={id} id={id}>
|
||||
{tab.content}
|
||||
</TabPanel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab && (
|
||||
<div style={{ marginTop: 8, width: '100%' }}>{activeTab.content}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
.customTabsContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
.root {
|
||||
&.fullWidth {
|
||||
:global(.react-aria-Tabs) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.react-aria-Tab) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.react-aria-Tabs) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
|
||||
&[data-orientation='horizontal'] {
|
||||
flex-direction: column;
|
||||
@@ -15,36 +21,39 @@
|
||||
|
||||
:global(.react-aria-TabList) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
:global(.react-aria-Tab) {
|
||||
display: flex;
|
||||
padding: 0 10px 10px 10px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
max-width: 300px;
|
||||
color: var(--c--contextuals--content--semantic--brand--tertiary);
|
||||
outline: none;
|
||||
position: relative;
|
||||
color: var(--c--theme--colors--secondary-900);
|
||||
transition: color 200ms;
|
||||
--border-color: transparent;
|
||||
forced-color-adjust: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--c--globals--spacings--sm);
|
||||
border-bottom: 1px solid var(--c--contextuals--border--surface--primary);
|
||||
|
||||
&[data-hovered] {
|
||||
background-color: #fff;
|
||||
color: var(--text-color-hover);
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--overlay--primary
|
||||
);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
font-weight: 500;
|
||||
color: var(--c--theme--colors--primary-text);
|
||||
border-bottom: 2px solid var(--c--theme--colors--primary-text);
|
||||
padding-bottom: 9px;
|
||||
border-bottom: 2px solid
|
||||
var(--c--contextuals--content--semantic--brand--tertiary);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
color: var(--text-color-disabled);
|
||||
color: var(--c--theme--colors--gray-400);
|
||||
&[data-selected] {
|
||||
--border-color: var(--text-color-disabled);
|
||||
--border-color: var(--c--theme--colors--gray-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,17 +62,72 @@
|
||||
position: absolute;
|
||||
inset: 4px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid var(--focus-ring-color);
|
||||
border: 2px solid var(--c--theme--colors--brand-600);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.react-aria-TabPanel) {
|
||||
margin-top: 15px;
|
||||
margin-top: 4px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
|
||||
&[data-focus-visible] {
|
||||
outline: 2px solid var(--focus-ring-color);
|
||||
outline: 2px solid var(--c--theme--colors--brand-600);
|
||||
}
|
||||
}
|
||||
|
||||
&.neutral {
|
||||
:global(.react-aria-Tab) {
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
&[data-selected] {
|
||||
border-color: var(
|
||||
--c--contextuals--content--semantic--neutral--tertiary
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.orientationVertical {
|
||||
:global(.react-aria-Tabs) {
|
||||
width: 100%;
|
||||
}
|
||||
:global(.react-aria-TabList) {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
:global(.react-aria-Tab) {
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
border-radius: 4px;
|
||||
border: none !important;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
font-size: var(--c--globals--font--sizes--sm);
|
||||
line-height: var(--c--globals--font--sizes--md);
|
||||
|
||||
&[data-hovered] {
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--overlay--primary
|
||||
);
|
||||
}
|
||||
|
||||
:global(.react-aria-Tab__subtext) {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
line-height: var(--c--globals--font--sizes--md);
|
||||
font-size: var(--c--globals--font--sizes--xs);
|
||||
color: var(--c--contextuals--content--semantic--neutral--secondary);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
font-weight: 700;
|
||||
padding-bottom: 10px;
|
||||
background-color: var(
|
||||
--c--contextuals--background--semantic--overlay--primary
|
||||
);
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CunninghamProvider } from '@openfun/cunningham-react';
|
||||
import { CunninghamProvider } from '@gouvfr-lasuite/ui-kit';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
|
||||
@@ -27,13 +27,13 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
const { theme } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools />
|
||||
<CunninghamProvider theme={theme}>
|
||||
<CunninghamProvider theme={theme}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools />
|
||||
<ConfigProvider>
|
||||
<Auth>{children}</Auth>
|
||||
</ConfigProvider>
|
||||
</CunninghamProvider>
|
||||
</QueryClientProvider>
|
||||
</QueryClientProvider>
|
||||
</CunninghamProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Loader } from '@openfun/cunningham-react';
|
||||
import { Loader } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAuthStore } from '@/core/auth';
|
||||
@@ -8,7 +8,12 @@ export const ButtonLogin = () => {
|
||||
const { logout } = useAuthStore();
|
||||
|
||||
return (
|
||||
<Button onClick={logout} color="primary-text" aria-label={t('Logout')}>
|
||||
<Button
|
||||
onClick={logout}
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
aria-label={t('Logout')}
|
||||
>
|
||||
{t('Logout')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,46 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { UserMenu } from '@gouvfr-lasuite/ui-kit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { baseApiUrl } from '@/api';
|
||||
import { useAuthStore } from '@/core/auth';
|
||||
import { LanguagePicker } from '@/features/language';
|
||||
|
||||
interface UserMenuUser {
|
||||
email: string;
|
||||
full_name: string;
|
||||
organization?: string;
|
||||
}
|
||||
|
||||
export const UserInfo = () => {
|
||||
const { t } = useTranslation();
|
||||
const { authenticated, userData } = useAuthStore();
|
||||
const userName = userData?.name || userData?.email || t('No Username');
|
||||
const organizationName = userData?.organization?.name || '';
|
||||
const { authenticated, userData, logout } = useAuthStore();
|
||||
const user: UserMenuUser = {
|
||||
email: userData?.email ?? '',
|
||||
full_name: userData?.name ?? '',
|
||||
organization:
|
||||
typeof userData?.organization === 'string'
|
||||
? userData.organization
|
||||
: (userData?.organization?.name ?? ''),
|
||||
};
|
||||
|
||||
return authenticated ? (
|
||||
<Box $direction="column" $align="left">
|
||||
<Text $theme="primary">
|
||||
{userName}
|
||||
{organizationName ? ` | ${organizationName}` : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
) : null;
|
||||
if (!authenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
return (
|
||||
<Button
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
onClick={() =>
|
||||
window.location.assign(new URL('authenticate/', baseApiUrl()).href)
|
||||
}
|
||||
>
|
||||
{t('Sign in')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return <UserMenu user={user} logout={logout} actions={<LanguagePicker />} />;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Loader } from '@openfun/cunningham-react';
|
||||
import { Loader } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* Select
|
||||
**/
|
||||
--c--components--forms-select--value-color--disabled: var(
|
||||
--c--theme--colors--greyscale-400
|
||||
--c--theme--colors--gray-400
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import url('@openfun/cunningham-react/icons');
|
||||
@import url('@openfun/cunningham-react/style');
|
||||
@import url('@openfun/cunningham-react/fonts');
|
||||
@import url('@gouvfr-lasuite/cunningham-react/icons');
|
||||
@import url('@gouvfr-lasuite/cunningham-react/style');
|
||||
@import url('@gouvfr-lasuite/cunningham-react/fonts');
|
||||
@import url('./cunningham-tokens.css');
|
||||
@import url('./cunningham-custom-tokens.css');
|
||||
@import url('../assets/fonts/Marianne/Marianne-font.css');
|
||||
@@ -9,7 +9,7 @@
|
||||
.c__field,
|
||||
.c__select,
|
||||
.c__datagrid {
|
||||
font-family: var(--c--theme--font--families--base);
|
||||
font-family: var(--c--globals--font--families--base);
|
||||
}
|
||||
|
||||
.c__field {
|
||||
@@ -24,16 +24,16 @@
|
||||
}
|
||||
|
||||
.labelled-box label {
|
||||
color: var(--c--theme--colors--greyscale-500);
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.labelled-box--disabled label {
|
||||
color: var(--c--components--forms-labelledbox--label-color--small-disabled);
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
}
|
||||
|
||||
.labelled-box label.placeholder {
|
||||
top: 12px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
.c__field :not(.c__textarea__wrapper, div) .labelled-box label.placeholder {
|
||||
@@ -48,11 +48,10 @@
|
||||
.c__input__wrapper,
|
||||
.c__textarea__wrapper {
|
||||
width: 100%;
|
||||
transition: all var(--c--theme--transitions--duration)
|
||||
var(--c--theme--transitions--ease-out);
|
||||
transition: all var(--c--globals--transitions--duration)
|
||||
var(--c--globals--transitions--ease-out);
|
||||
gap: 0.5rem;
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
color: var(--c--globals--colors--gray-600);
|
||||
}
|
||||
|
||||
@media screen and (width <= 768px) {
|
||||
@@ -93,7 +92,7 @@
|
||||
}
|
||||
|
||||
.c__input__wrapper--disabled .c__input {
|
||||
color: var(--c--components--forms-input--value-color--disabled);
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
}
|
||||
|
||||
.c__input__wrapper--error .c__input {
|
||||
@@ -148,8 +147,8 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__select__wrapper {
|
||||
transition: all var(--c--theme--transitions--duration)
|
||||
var(--c--theme--transitions--ease-out);
|
||||
transition: all var(--c--globals--transitions--duration)
|
||||
var(--c--globals--transitions--ease-out);
|
||||
min-height: var(--c--components--forms-select--height);
|
||||
height: auto;
|
||||
}
|
||||
@@ -174,8 +173,8 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__select__menu__item {
|
||||
transition: all var(--c--theme--transitions--duration)
|
||||
var(--c--theme--transitions--ease-out);
|
||||
transition: all var(--c--globals--transitions--duration)
|
||||
var(--c--globals--transitions--ease-out);
|
||||
}
|
||||
|
||||
.c__select--disabled .c__select__wrapper label,
|
||||
@@ -239,17 +238,17 @@ input:-webkit-autofill:focus {
|
||||
|
||||
.c__datagrid__table__container > table tbody tr {
|
||||
border: none;
|
||||
border-top: 1px var(--c--theme--colors--greyscale-100) solid;
|
||||
border-bottom: 1px var(--c--theme--colors--greyscale-100) solid;
|
||||
border-top: 1px solid var(--c--contextuals--border--surface--primary);
|
||||
border-bottom: 1px solid var(--c--contextuals--border--surface--primary);
|
||||
}
|
||||
|
||||
.c__datagrid__table__container > table tbody tr:hover {
|
||||
background-color: var(--c--theme--colors--greyscale-050);
|
||||
background-color: var(--c--contextuals--background--surface--secondary);
|
||||
}
|
||||
|
||||
.c__datagrid__table__container > table tbody {
|
||||
background-color: var(--c--components--datagrid--body--background-color);
|
||||
color: var(--c--theme--colors--greyscale-900);
|
||||
color: var(--c--globals--colors--gray-900);
|
||||
}
|
||||
|
||||
.c__datagrid__table__container > table {
|
||||
@@ -286,7 +285,7 @@ input:-webkit-autofill:focus {
|
||||
background-color: var(
|
||||
--c--components--datagrid--pagination--background-color-active
|
||||
);
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
color: var(--c--globals--colors--gray-800);
|
||||
}
|
||||
|
||||
.c__pagination__list .c__button--tertiary-text:disabled {
|
||||
@@ -309,22 +308,22 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__date-picker__wrapper {
|
||||
transition: all var(--c--theme--transitions--duration)
|
||||
var(--c--theme--transitions--ease-out);
|
||||
transition: all var(--c--globals--transitions--duration)
|
||||
var(--c--globals--transitions--ease-out);
|
||||
}
|
||||
|
||||
.c__date-picker:not(.c__date-picker--disabled):hover .c__date-picker__wrapper {
|
||||
box-shadow: var(--c--theme--colors--primary-500) 0 0 0 2px;
|
||||
box-shadow: var(--c--globals--colors--primary-500) 0 0 0 2px;
|
||||
}
|
||||
|
||||
.c__date-picker.c__date-picker--invalid:not(.c__date-picker--disabled):hover
|
||||
.c__date-picker__wrapper {
|
||||
box-shadow: var(--c--theme--colors--danger-300) 0 0 0 2px;
|
||||
box-shadow: var(--c--globals--colors--danger-300) 0 0 0 2px;
|
||||
}
|
||||
|
||||
.c__date-picker__wrapper button[aria-label='Clear date'],
|
||||
.c__date-picker.c__date-picker--invalid .c__date-picker__wrapper * {
|
||||
color: var(--c--theme--colors--danger-300);
|
||||
color: var(--c--globals--colors--danger-300);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +344,7 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__checkbox.c__checkbox--disabled .c__field__text {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
color: var(--c--globals--colors--gray-600);
|
||||
}
|
||||
|
||||
.c__switch.c__checkbox--disabled .c__switch__rail {
|
||||
@@ -353,11 +352,11 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__checkbox.c__checkbox--disabled .c__checkbox__label {
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
color: var(--c--globals--colors--gray-400);
|
||||
}
|
||||
|
||||
.c__radio input::before {
|
||||
box-shadow: inset 1em 1em var(--c--theme--colors--primary-600);
|
||||
box-shadow: inset 1em 1em var(--c--globals--colors--primary-600);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,8 +550,8 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__button--with-icon--right {
|
||||
padding: 0.7rem var(--c--theme--spacings--t) 0.7rem
|
||||
var(--c--theme--spacings--s);
|
||||
padding: 0.7rem var(--c--globals--spacings--t) 0.7rem
|
||||
var(--c--globals--spacings--s);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,37 +566,28 @@ input:-webkit-autofill:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.c__modal__close button {
|
||||
padding: 0;
|
||||
font-size: 88px;
|
||||
width: 28px !important;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.c__modal__close button .material-icons {
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
}
|
||||
|
||||
.c__modal__close .c__button {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.c__modal--full .c__modal__content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.c__modal__content {
|
||||
color: var(--c--theme--colors--greyscale-600) !important;
|
||||
font-size: var(--c--globals--font--sizes--sm);
|
||||
color: var(
|
||||
--c--contextuals--content--semantic--neutral--secondary
|
||||
) !important;
|
||||
}
|
||||
|
||||
.c__modal__content__margin {
|
||||
padding: var(--c--globals--spacings--md);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.c__modal__title {
|
||||
padding: var(--c--theme--spacings--md);
|
||||
padding: var(--c--globals--spacings--md);
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
font-size: 1.125rem;
|
||||
font-size: var(--c--globals--font--sizes--lg) !important;
|
||||
}
|
||||
|
||||
@media screen and (width <= 420px) {
|
||||
@@ -614,7 +604,7 @@ input:-webkit-autofill:focus {
|
||||
.c__modal__footer {
|
||||
margin-top: 0;
|
||||
background-color: transparent !important;
|
||||
padding: var(--c--theme--spacings--md);
|
||||
padding: var(--c--globals--spacings--md);
|
||||
}
|
||||
|
||||
@media screen and (width <= 576px) {
|
||||
@@ -638,8 +628,8 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.c__modal__title {
|
||||
font-size: var(--c--theme--font--sizes--xs);
|
||||
padding: var(--c--theme--spacings--base);
|
||||
font-size: var(--c--globals--font--sizes--xs);
|
||||
padding: var(--c--globals--spacings--base);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,17 +3,27 @@ import { create } from 'zustand';
|
||||
|
||||
import { tokens } from './cunningham-tokens';
|
||||
|
||||
type Tokens = typeof tokens.themes.default;
|
||||
type ColorsTokens = Tokens['theme']['colors'];
|
||||
type FontSizesTokens = Tokens['theme']['font']['sizes'];
|
||||
type SpacingsTokens = Tokens['theme']['spacings'];
|
||||
type ComponentTokens = Tokens['components'];
|
||||
type Tokens = typeof tokens.themes.default &
|
||||
Partial<(typeof tokens.themes)[keyof typeof tokens.themes]>;
|
||||
type ColorsTokens = Tokens['globals']['colors'];
|
||||
type FontSizesTokens = Tokens['globals']['font']['sizes'];
|
||||
type SpacingsTokens = Tokens['globals']['spacings'];
|
||||
type ComponentTokens = Partial<
|
||||
| (Tokens['components'] & Tokens['globals']['components'])
|
||||
| Record<string, unknown>
|
||||
> &
|
||||
Record<string, unknown>;
|
||||
|
||||
export const removeQuotes = (str: string) => {
|
||||
return str.replace(/^['"]|['"]$/g, '');
|
||||
};
|
||||
|
||||
export type Theme = keyof typeof tokens.themes;
|
||||
|
||||
interface AuthStore {
|
||||
theme: string;
|
||||
setTheme: (theme: Theme) => void;
|
||||
themeTokens: () => Partial<Tokens['theme']>;
|
||||
themeTokens: () => Partial<Tokens['globals']>;
|
||||
colorsTokens: () => Partial<ColorsTokens>;
|
||||
fontSizesTokens: () => Partial<FontSizesTokens>;
|
||||
spacingsTokens: () => Partial<SpacingsTokens>;
|
||||
@@ -29,11 +39,11 @@ export const useCunninghamTheme = create<AuthStore>((set, get) => {
|
||||
|
||||
return {
|
||||
theme: 'dsfr',
|
||||
themeTokens: () => currentTheme().theme,
|
||||
colorsTokens: () => currentTheme().theme.colors,
|
||||
themeTokens: () => currentTheme().globals,
|
||||
colorsTokens: () => currentTheme().globals?.colors ?? {},
|
||||
componentTokens: () => currentTheme().components,
|
||||
spacingsTokens: () => currentTheme().theme.spacings,
|
||||
fontSizesTokens: () => currentTheme().theme.font.sizes,
|
||||
spacingsTokens: () => currentTheme().globals?.spacings ?? {},
|
||||
fontSizesTokens: () => currentTheme().globals?.font?.sizes ?? {},
|
||||
setTheme: (theme: Theme) => {
|
||||
set({ theme });
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ const BlueStripe = styled.div`
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background: var(--c--theme--colors--primary-600);
|
||||
background: var(--c--theme--colors--brand-600);
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
@@ -82,7 +82,7 @@ export const Footer = () => {
|
||||
gap:0.2rem;
|
||||
transition: box-shadow 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
|
||||
box-shadow: 0px 2px 0 0 var(--c--theme--colors--gray-text);
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -98,7 +98,7 @@ export const Footer = () => {
|
||||
$padding={{ top: 'tiny' }}
|
||||
$css="
|
||||
flex-wrap: wrap;
|
||||
border-top: 1px solid var(--c--theme--colors--greyscale-200);
|
||||
border-top: 1px solid var(--c--theme--colors--gray-200);
|
||||
column-gap: 1rem;
|
||||
row-gap: .5rem;
|
||||
"
|
||||
|
||||
@@ -1,116 +1,84 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import Image from 'next/image';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { default as IconRegie } from '@/assets/logo-regie.svg?url';
|
||||
import { Icon, StyledLink, Text } from '@/components/';
|
||||
import { ButtonLogin } from '@/core/auth';
|
||||
import { StyledLink } from '@/components/';
|
||||
import { UserInfo } from '@/core/auth/UserInfo';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { LanguagePicker } from '@/features/language';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
import { TogglePanelButton } from '@/features/left-panel/components/TogglePanelButton';
|
||||
|
||||
import { LaGaufre } from './LaGaufre';
|
||||
export const HEADER_HEIGHT = '52px';
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useCunninghamTheme();
|
||||
const { isPanelOpen, togglePanel } = useLeftPanelStore();
|
||||
|
||||
const spacings = theme.spacingsTokens();
|
||||
const colors = theme.colorsTokens();
|
||||
const { isPanelOpen } = useLeftPanelStore();
|
||||
|
||||
return (
|
||||
<header
|
||||
className=""
|
||||
style={{
|
||||
display: 'flex',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
position: 'sticky',
|
||||
top: '0px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
flexDirection: 'row',
|
||||
padding: '10px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: 'var(--header-height)',
|
||||
minHeight: 'var(--header-height)',
|
||||
padding: `0 ${spacings['base']}`,
|
||||
backgroundColor: colors['greyscale-000'],
|
||||
borderBottom: `1px solid ${colors['greyscale-200']}`,
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0.00) 100%)',
|
||||
backdropFilter: 'blur(1px)',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className="md:hidden"
|
||||
size="medium"
|
||||
onClick={() => togglePanel()}
|
||||
aria-label={t('Open the header menu')}
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Icon
|
||||
$variation="800"
|
||||
$theme="primary"
|
||||
iconName={isPanelOpen ? 'close' : 'menu'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<StyledLink href="/">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderRadius: '8px',
|
||||
backgroundColor:
|
||||
'var(--c--contextuals--background--surface--primary)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
border: '1px solid var(--c--contextuals--border--surface--primary)',
|
||||
padding: '4px',
|
||||
flexDirection: 'row',
|
||||
boxShadow: '0 2px 4px 0 rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
<TogglePanelButton />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: spacings['3xs'],
|
||||
overflow: 'hidden',
|
||||
maxWidth: isPanelOpen ? '0' : '120px',
|
||||
opacity: isPanelOpen ? '0' : '1',
|
||||
transition: 'max-width 0.3s ease, opacity 0.2s ease 0.3s',
|
||||
}}
|
||||
>
|
||||
<Image priority src={IconRegie} alt={t('La régie Logo')} width={25} />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: spacings['xs'],
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
as="h2"
|
||||
style={{ color: '#000091', zIndex: 1, fontSize: '1.30rem' }}
|
||||
>
|
||||
{t('La Régie')}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
padding: '1px 8px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 'bold',
|
||||
color: colors['primary-500'],
|
||||
borderRadius: '12px',
|
||||
backgroundColor: colors['primary-200'],
|
||||
}}
|
||||
>
|
||||
BETA
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</StyledLink>
|
||||
<div className="md:hidden">
|
||||
<div
|
||||
style={{ display: 'flex', flexDirection: 'row', gap: spacings['sm'] }}
|
||||
>
|
||||
<LaGaufre />
|
||||
<StyledLink href="/">
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Image priority src={IconRegie} alt="Régie Logo" height={34} />
|
||||
</div>
|
||||
</StyledLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
|
||||
<div className="">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: spacings['sm'],
|
||||
borderRadius: '8px',
|
||||
backgroundColor:
|
||||
'var(--c--contextuals--background--surface--primary)',
|
||||
border: '1px solid var(--c--contextuals--border--surface--primary)',
|
||||
padding: '4px',
|
||||
gap: '4px',
|
||||
flexDirection: 'row',
|
||||
boxShadow: '0 2px 4px 0 rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
<UserInfo />
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
<LaGaufre />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,114 @@
|
||||
import { Gaufre } from '@gouvfr-lasuite/integration';
|
||||
import '@gouvfr-lasuite/integration/dist/css/gaufre.css';
|
||||
// import { Gaufre } from '@gouvfr-lasuite/integration';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const GaufreStyle = createGlobalStyle`
|
||||
.lasuite-gaufre-btn{
|
||||
box-shadow: inset 0 0 0 0 !important;
|
||||
declare global {
|
||||
interface Window {
|
||||
_lasuite_widget?: unknown[];
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
export const LaGaufre = () => {
|
||||
useEffect(() => {
|
||||
const wrapper = document.querySelector('[data-gaufre-button-wrapper]');
|
||||
const button = wrapper?.querySelector('button') as HTMLButtonElement;
|
||||
if (button && !button.id) {
|
||||
button.id = 'gaufre_button';
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
const applyZIndex = () => {
|
||||
const shadowHost = document.querySelector(
|
||||
'#lasuite-widget-lagaufre-shadow',
|
||||
);
|
||||
const wrapper = shadowHost?.shadowRoot?.querySelector(
|
||||
'#wrapper',
|
||||
) as HTMLElement;
|
||||
if (wrapper) {
|
||||
wrapper.style.zIndex = '1000000000';
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(applyZIndex, 500);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-gaufre-button-wrapper>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="!w-10 !h-10 !p-0 !min-w-0"
|
||||
aria-label="Les services de LaSuite"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
fill="var(--c--contextuals--content--semantic--brand--tertiary)"
|
||||
id="square"
|
||||
d="M2.7959 0.5C3.26483 0.5 3.49956 0.49985 3.68848 0.564453C4.03934 0.684581 4.31542 0.960658 4.43555 1.31152C4.50015 1.50044 4.5 1.73517 4.5 2.2041V2.7959C4.5 3.26483 4.50015 3.49956 4.43555 3.68848C4.31542 4.03934 4.03934 4.31542 3.68848 4.43555C3.49956 4.50015 3.26483 4.5 2.7959 4.5H2.2041C1.73517 4.5 1.50044 4.50015 1.31152 4.43555C0.960658 4.31542 0.684581 4.03934 0.564453 3.68848C0.49985 3.49956 0.5 3.26483 0.5 2.7959V2.2041C0.5 1.73517 0.49985 1.50044 0.564453 1.31152C0.684581 0.960658 0.960658 0.684581 1.31152 0.564453C1.50044 0.49985 1.73517 0.5 2.2041 0.5H2.7959Z"
|
||||
/>
|
||||
</defs>
|
||||
<use href="#square" transform="translate(0, 0)" />
|
||||
<use href="#square" transform="translate(6.5, 0)" />
|
||||
<use href="#square" transform="translate(13, 0)" />
|
||||
<use href="#square" transform="translate(0, 6.5)" />
|
||||
<use href="#square" transform="translate(6.5, 6.5)" />
|
||||
<use href="#square" transform="translate(13, 6.5)" />
|
||||
<use href="#square" transform="translate(0, 13)" />
|
||||
<use href="#square" transform="translate(6.5, 13)" />
|
||||
<use href="#square" transform="translate(13, 13)" />
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
<Script
|
||||
src="https://integration.lasuite.numerique.gouv.fr/api/v1/gaufre.js"
|
||||
src="https://static.suite.anct.gouv.fr/widgets/lagaufre.js"
|
||||
strategy="lazyOnload"
|
||||
id="lasuite-gaufre-script"
|
||||
onLoad={() => {
|
||||
const button = document.getElementById('gaufre_button');
|
||||
if (button) {
|
||||
globalThis.window._lasuite_widget =
|
||||
globalThis.window._lasuite_widget || [];
|
||||
globalThis.window._lasuite_widget.push([
|
||||
'lagaufre',
|
||||
'init',
|
||||
{
|
||||
api: 'https://lasuite.numerique.gouv.fr/api/services',
|
||||
label: 'Services de la Suite numérique',
|
||||
closeLabel: 'Fermer le menu',
|
||||
headerLabel: 'À propos',
|
||||
backgroundColor: '#fff',
|
||||
background:
|
||||
'linear-gradient(#f1f2fd, rgba(255, 255, 255, 1) 20.54%, #FFF 0%',
|
||||
headerLogo:
|
||||
'https://lasuite.numerique.gouv.fr/assets/lasuite.svg',
|
||||
headerUrl: 'https://lasuite.numerique.gouv.fr',
|
||||
loadingText: 'Chargement…',
|
||||
newWindowLabelSuffix: ' (nouvelle fenêtre)',
|
||||
fontFamily: 'Marianne',
|
||||
buttonElement: button,
|
||||
viewMoreLabel: 'Voir plus',
|
||||
viewLessLabel: 'Voir moins',
|
||||
position: () => {
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
position: 'fixed',
|
||||
top: 65,
|
||||
right: 20,
|
||||
};
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<GaufreStyle />
|
||||
<Gaufre variant="small" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,63 +1,29 @@
|
||||
import { Select } from '@openfun/cunningham-react';
|
||||
import { LanguagePicker as LanguagePickerUi } from '@gouvfr-lasuite/ui-kit';
|
||||
import { Settings } from 'luxon';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components/';
|
||||
import { LANGUAGES_ALLOWED } from '@/i18n/conf';
|
||||
|
||||
const SelectStyled = styled(Select)<{ $isSmall?: boolean }>`
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
|
||||
.c__select__wrapper {
|
||||
min-height: 2rem;
|
||||
height: auto;
|
||||
border-color: transparent;
|
||||
padding: 0 0.15rem 0 0.45rem;
|
||||
border-radius: 1px;
|
||||
|
||||
.labelled-box .labelled-box__children {
|
||||
padding-right: 2rem;
|
||||
|
||||
.c_select__render .typo-text {
|
||||
${({ $isSmall }) => $isSmall && `display: none;`}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const LanguagePicker = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { i18n } = useTranslation();
|
||||
const { preload: languages } = i18n.options;
|
||||
Settings.defaultLocale = i18n.language;
|
||||
|
||||
const optionsPicker = useMemo(() => {
|
||||
return (languages || []).map((lang) => ({
|
||||
value: lang,
|
||||
label: lang,
|
||||
label: LANGUAGES_ALLOWED[lang],
|
||||
render: () => (
|
||||
<Box
|
||||
className="c_select__render"
|
||||
$direction="row"
|
||||
$gap="0.7rem"
|
||||
$align="center"
|
||||
$css="text-transform: uppercase;"
|
||||
>
|
||||
<Text
|
||||
$isMaterialIcon
|
||||
$size="1rem"
|
||||
$theme="primary"
|
||||
$weight="bold"
|
||||
$variation="800"
|
||||
>
|
||||
translate
|
||||
</Text>
|
||||
<Text $theme="primary" $weight="500" $variation="800">
|
||||
<Text $theme="brand" $weight="500" $variation="800">
|
||||
{LANGUAGES_ALLOWED[lang]}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -71,33 +37,33 @@ export const LanguagePicker = () => {
|
||||
}
|
||||
}, [i18n.language]);
|
||||
|
||||
/**
|
||||
* @description prevent select div to receive focus on keyboard navigation so the focus goes directly to inner button
|
||||
* @see https://github.com/suitenumerique/people/pull/379
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
document
|
||||
.querySelector('.c__select-language-picker .c__select__wrapper')
|
||||
?.setAttribute('tabindex', '-1');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SelectStyled
|
||||
label={t('Language')}
|
||||
showLabelWhenSelected={false}
|
||||
clearable={false}
|
||||
hideLabel
|
||||
defaultValue={i18n.language}
|
||||
className="c_select__no_bg"
|
||||
options={optionsPicker}
|
||||
onChange={(e) => {
|
||||
i18n.changeLanguage(e.target.value as string).catch((err) => {
|
||||
<LanguagePickerUi
|
||||
languages={optionsPicker}
|
||||
size="small"
|
||||
onChange={(selected) => {
|
||||
type LanguageOption = { value?: string };
|
||||
let code: string | undefined;
|
||||
|
||||
if (typeof selected === 'string') {
|
||||
code = selected;
|
||||
} else if (
|
||||
typeof selected === 'object' &&
|
||||
selected !== null &&
|
||||
typeof (selected as LanguageOption).value === 'string'
|
||||
) {
|
||||
code = (selected as LanguageOption).value;
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
|
||||
i18n.changeLanguage(code).catch((err) => {
|
||||
console.error('Error changing language', err);
|
||||
});
|
||||
}}
|
||||
compact
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import Image from 'next/image';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { default as IconRegie } from '@/assets/logo-regie.svg?url';
|
||||
import { StyledLink } from '@/components/';
|
||||
import { SeparatedSection } from '@/components/separators';
|
||||
import { ButtonLogin } from '@/core';
|
||||
import { UserInfo } from '@/core/auth/UserInfo';
|
||||
import { LanguagePicker } from '@/features/language';
|
||||
|
||||
import { useLeftPanelStore } from '../stores';
|
||||
|
||||
import { LeftPanelContent } from './LeftPanelContent';
|
||||
import { LeftPanelHeader } from './LeftPanelHeader';
|
||||
import { LeftPanelItems } from './LeftPanelItems';
|
||||
|
||||
const MobileLeftPanelStyle = createGlobalStyle`
|
||||
@@ -21,11 +20,15 @@ const MobileLeftPanelStyle = createGlobalStyle`
|
||||
|
||||
const LeftPanelStyle = createGlobalStyle`
|
||||
.LeftPanel {
|
||||
height: calc(100vh - var(--header-height));
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 12px;
|
||||
height: 100vh;
|
||||
width: 300px;
|
||||
min-width: 300px;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--c--theme--colors--greyscale-200']};
|
||||
background-color: var(--c--contextuals--background--surface--tertiary);
|
||||
border-right: 1px solid var(--c--contextuals--border--surface--primary);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -34,29 +37,63 @@ export const LeftPanel = () => {
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
const closePanel = useCallback(() => {
|
||||
togglePanel(false);
|
||||
}, [togglePanel]);
|
||||
|
||||
useEffect(() => {
|
||||
toggle();
|
||||
}, [pathname, toggle]);
|
||||
if (
|
||||
typeof globalThis.window !== 'undefined' &&
|
||||
globalThis.window.matchMedia('(min-width: 768px)').matches
|
||||
) {
|
||||
togglePanel(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof globalThis.window !== 'undefined' &&
|
||||
!globalThis.window.matchMedia('(min-width: 768px)').matches
|
||||
) {
|
||||
closePanel();
|
||||
}
|
||||
}, [pathname, closePanel]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden md:block">
|
||||
<div
|
||||
className="hidden md:block"
|
||||
style={{
|
||||
width: isPanelOpen ? undefined : 0,
|
||||
minWidth: isPanelOpen ? undefined : 0,
|
||||
overflow: 'hidden',
|
||||
transition: 'min-width 0.2s ease, width 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<LeftPanelStyle />
|
||||
<div className="LeftPanel">
|
||||
<div className="LeftPanel" style={{ minWidth: isPanelOpen ? 300 : 0 }}>
|
||||
<div
|
||||
style={{
|
||||
flex: '0 0 auto',
|
||||
}}
|
||||
></div>
|
||||
<StyledLink href="/">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '1rem',
|
||||
}}
|
||||
>
|
||||
<Image priority src={IconRegie} alt="Régie" height={40} />
|
||||
</div>
|
||||
</StyledLink>
|
||||
<menu
|
||||
style={{
|
||||
margin: '0',
|
||||
gap: 'var(--c--theme--spacings--xs)',
|
||||
padding: '0 10px',
|
||||
padding: '0',
|
||||
}}
|
||||
>
|
||||
<LeftPanelItems />
|
||||
@@ -71,11 +108,18 @@ export const LeftPanel = () => {
|
||||
style={{
|
||||
zIndex: '999',
|
||||
width: '100vw',
|
||||
height: 'calc(100dvh - var(--header-height))',
|
||||
borderRight: '1px solid var(--c--theme--colors--greyscale-200)',
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
padding: '12px',
|
||||
backgroundColor:
|
||||
'var(--c--contextuals--background--surface--tertiary)',
|
||||
transform: `translateX(${isPanelOpen ? '0' : '-100dvw'})`,
|
||||
backgroundColor: 'var(--c--theme--colors--greyscale-000)',
|
||||
transition: 'transform 0.2s ease-out',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
className="md:hidden"
|
||||
>
|
||||
@@ -83,13 +127,17 @@ export const LeftPanel = () => {
|
||||
data-testid="left-panel-mobile"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '0 10px',
|
||||
justifyContent: 'center',
|
||||
marginTop: '60px',
|
||||
justifyContent: 'flex-start',
|
||||
gap: 'var(--c--theme--spacings--base)',
|
||||
}}
|
||||
className="md:hidden"
|
||||
>
|
||||
<LeftPanelHeader />
|
||||
<StyledLink href="/">
|
||||
<div>
|
||||
<Image priority src={IconRegie} alt="Régie" height={40} />
|
||||
</div>
|
||||
</StyledLink>
|
||||
<LeftPanelItems />
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<div
|
||||
@@ -99,11 +147,7 @@ export const LeftPanel = () => {
|
||||
flexDirection: 'column',
|
||||
gap: 'var(--c--theme--spacings--sm)',
|
||||
}}
|
||||
>
|
||||
<UserInfo />
|
||||
<ButtonLogin />
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
></div>
|
||||
</SeparatedSection>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Icon, SeparatedSection } from '@/components';
|
||||
// import { useAuthStore } from '@/core/auth/useAuthStore';
|
||||
|
||||
import { useLeftPanelStore } from '../stores';
|
||||
|
||||
export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
// const auth = useAuthStore();
|
||||
const { togglePanel } = useLeftPanelStore();
|
||||
|
||||
const goToHome = () => {
|
||||
router.push('/');
|
||||
togglePanel();
|
||||
};
|
||||
|
||||
// const createNewDoc = () => {
|
||||
// createDoc({ title: t('Untitled document') });
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<SeparatedSection>
|
||||
<div>
|
||||
<Button
|
||||
onClick={goToHome}
|
||||
size="medium"
|
||||
color="tertiary-text"
|
||||
icon={<Icon $variation="800" $theme="primary" iconName="house" />}
|
||||
/>
|
||||
{/* {auth.authenticated && (
|
||||
<Button
|
||||
onClick={searchModal.open}
|
||||
size="medium"
|
||||
color="tertiary-text"
|
||||
icon={
|
||||
<Icon $variation="800" $theme="primary" iconName="search" />
|
||||
}
|
||||
/>
|
||||
)}*/}
|
||||
</div>
|
||||
{/* {auth.authenticated && (
|
||||
<Button onClick={createNewDoc}>{t('New doc')}</Button>
|
||||
)}*/}
|
||||
</SeparatedSection>
|
||||
{children}
|
||||
</div>
|
||||
{/* {searchModal.isOpen && (
|
||||
<DocSearchModal {...searchModal} size={ModalSize.LARGE} />
|
||||
)}*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,28 +1,23 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon, Text } from '@/components';
|
||||
import { SeparatedSection } from '@/components/separators';
|
||||
import { useAuthStore } from '@/core/auth';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
|
||||
export const LeftPanelItems = () => {
|
||||
const { t } = useTranslation();
|
||||
const { closePanel } = useLeftPanelStore();
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const { userData } = useAuthStore();
|
||||
|
||||
const colors = colorsTokens();
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const defaultQueries = [
|
||||
{
|
||||
icon: 'group',
|
||||
label: t('Teams'),
|
||||
label: t('Groups'),
|
||||
targetQuery: '/teams',
|
||||
abilities: userData?.abilities?.teams.can_view,
|
||||
},
|
||||
@@ -36,7 +31,12 @@ export const LeftPanelItems = () => {
|
||||
|
||||
const onSelectQuery = (query: string) => {
|
||||
router.push(query);
|
||||
closePanel();
|
||||
if (
|
||||
typeof globalThis.window !== 'undefined' &&
|
||||
!globalThis.window.matchMedia('(min-width: 768px)').matches
|
||||
) {
|
||||
closePanel();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -47,7 +47,7 @@ export const LeftPanelItems = () => {
|
||||
gap: 'var(--c--theme--spacings--2xs)',
|
||||
}}
|
||||
>
|
||||
{defaultQueries.map((query, index) => {
|
||||
{defaultQueries.map((query) => {
|
||||
if (!query.abilities) {
|
||||
return null;
|
||||
}
|
||||
@@ -57,34 +57,32 @@ export const LeftPanelItems = () => {
|
||||
pathname.startsWith(`${query.targetQuery}/`);
|
||||
|
||||
return (
|
||||
<SeparatedSection
|
||||
<Button
|
||||
color="neutral"
|
||||
variant="tertiary"
|
||||
style={{
|
||||
backgroundColor: isActive
|
||||
? 'var(--c--contextuals--background--semantic--overlay--primary)'
|
||||
: '',
|
||||
fontWeight: isActive ? 'bold' : '500',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
key={query.label}
|
||||
showSeparator={defaultQueries.length - 1 > index}
|
||||
>
|
||||
<Button
|
||||
data-testid={`${query.label} button`}
|
||||
aria-label={`${query.label} button`}
|
||||
onClick={() => onSelectQuery(query.targetQuery)}
|
||||
style={{
|
||||
gap: 'var(--c--theme--spacings--xs)',
|
||||
margin: '10px 0',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
backgroundColor: isActive
|
||||
? colors['greyscale-100']
|
||||
: colors['greyscale-000'],
|
||||
fontWeight: isActive ? 700 : undefined,
|
||||
}}
|
||||
>
|
||||
data-testid={`${query.label} button`}
|
||||
aria-label={`${query.label} button`}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
onClick={() => onSelectQuery(query.targetQuery)}
|
||||
icon={
|
||||
<Icon
|
||||
$variation={isActive ? '1000' : '700'}
|
||||
$width="16"
|
||||
$height="16"
|
||||
$theme="neutral"
|
||||
iconName={query.icon}
|
||||
/>
|
||||
<Text $variation={isActive ? '1000' : '700'} $size="sm">
|
||||
{query.label}
|
||||
</Text>
|
||||
</Button>
|
||||
</SeparatedSection>
|
||||
}
|
||||
>
|
||||
<Text $size="sm">{query.label}</Text>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import IconPanelMenu from '@/assets/icons/icon-panel-menu.svg';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
|
||||
export const TogglePanelButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { isPanelOpen, togglePanel } = useLeftPanelStore();
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="medium"
|
||||
onClick={() => togglePanel()}
|
||||
aria-label={isPanelOpen ? t('Close the menu') : t('Open the menu')}
|
||||
color="neutral"
|
||||
variant="tertiary"
|
||||
icon={<IconPanelMenu width={24} height={24} aria-hidden />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from './LeftPanel';
|
||||
export * from './TogglePanelButton';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input } from '@openfun/cunningham-react';
|
||||
import { Input } from '@gouvfr-lasuite/cunningham-react';
|
||||
|
||||
interface InputUserEmailProps {
|
||||
label: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input } from '@openfun/cunningham-react';
|
||||
import { Input } from '@gouvfr-lasuite/cunningham-react';
|
||||
|
||||
interface InputUserEmailProps {
|
||||
label: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { InputUserEmail, InputUserPassword } from '@/features/login';
|
||||
@@ -82,7 +82,7 @@ export const LoginForm = ({
|
||||
)}
|
||||
|
||||
<Box $padding="tiny">
|
||||
<Button color="primary" type="submit" fullWidth>
|
||||
<Button color="brand" type="submit" fullWidth>
|
||||
{labelSignIn}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './useMailDomainAccesses';
|
||||
export * from './useInvitationMailDomainAccesses';
|
||||
export * from './useUpdateMailDomainAccess';
|
||||
export * from './useCreateMailDomainAccess';
|
||||
export * from './useDeleteMailDomainAccess';
|
||||
export * from './useDeleteMailDomainInvitation';
|
||||
export * from './useCreateInvitation';
|
||||
|
||||
+21
-2
@@ -1,10 +1,16 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import { User } from '@/core/auth';
|
||||
import {
|
||||
KEY_LIST_MAIL_DOMAIN,
|
||||
KEY_MAIL_DOMAIN,
|
||||
MailDomain,
|
||||
Role,
|
||||
} from '@/features/mail-domains/domains';
|
||||
import { Invitation, OptionType } from '@/features/teams/member-add/types';
|
||||
|
||||
import { MailDomain, Role } from '../../domains';
|
||||
import { KEY_LIST_INVITATION_DOMAIN_ACCESSES } from './useInvitationMailDomainAccesses';
|
||||
|
||||
interface CreateInvitationParams {
|
||||
email: User['email'];
|
||||
@@ -42,7 +48,20 @@ export const createInvitation = async ({
|
||||
};
|
||||
|
||||
export function useCreateInvitation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<Invitation, APIError, CreateInvitationParams>({
|
||||
mutationFn: createInvitation,
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_INVITATION_DOMAIN_ACCESSES],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_MAIL_DOMAIN],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_MAIL_DOMAIN],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
UseMutationOptions,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import {
|
||||
KEY_LIST_MAIL_DOMAIN,
|
||||
KEY_MAIL_DOMAIN,
|
||||
} from '@/features/mail-domains/domains';
|
||||
|
||||
import { KEY_LIST_INVITATION_DOMAIN_ACCESSES } from './useInvitationMailDomainAccesses';
|
||||
|
||||
interface DeleteMailDomainInvitationProps {
|
||||
slug: string;
|
||||
invitationId: string;
|
||||
}
|
||||
|
||||
export const deleteMailDomainInvitation = async ({
|
||||
slug,
|
||||
invitationId,
|
||||
}: DeleteMailDomainInvitationProps): Promise<void> => {
|
||||
const response = await fetchAPI(
|
||||
`mail-domains/${slug}/invitations/${invitationId}/`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError(
|
||||
'Failed to delete the invitation',
|
||||
await errorCauses(response),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type UseDeleteMailDomainInvitationOptions = UseMutationOptions<
|
||||
void,
|
||||
APIError,
|
||||
DeleteMailDomainInvitationProps
|
||||
>;
|
||||
|
||||
export const useDeleteMailDomainInvitation = (
|
||||
options?: UseDeleteMailDomainInvitationOptions,
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
onSuccess: optionsOnSuccess,
|
||||
onError: optionsOnError,
|
||||
...restOptions
|
||||
} = options || {};
|
||||
return useMutation<void, APIError, DeleteMailDomainInvitationProps>({
|
||||
mutationFn: deleteMailDomainInvitation,
|
||||
...restOptions,
|
||||
onSuccess: (data, variables, context) => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_INVITATION_DOMAIN_ACCESSES],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_MAIL_DOMAIN],
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_LIST_MAIL_DOMAIN],
|
||||
});
|
||||
if (optionsOnSuccess) {
|
||||
(
|
||||
optionsOnSuccess as unknown as (
|
||||
data: void,
|
||||
variables: DeleteMailDomainInvitationProps,
|
||||
context: unknown,
|
||||
) => void
|
||||
)(data, variables, context);
|
||||
}
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
if (optionsOnError) {
|
||||
(
|
||||
optionsOnError as unknown as (
|
||||
error: APIError,
|
||||
variables: DeleteMailDomainInvitationProps,
|
||||
context: unknown,
|
||||
) => void
|
||||
)(error, variables, context);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user