diff --git a/.github/workflows/crowdin_download.yml b/.github/workflows/crowdin_download.yml
index 6d9355cb..84a075bb 100644
--- a/.github/workflows/crowdin_download.yml
+++ b/.github/workflows/crowdin_download.yml
@@ -7,10 +7,11 @@ on:
- 'release/**'
jobs:
- install-front:
- uses: ./.github/workflows/front-dependencies-installation.yml
+ install-dependencies:
+ uses: ./.github/workflows/dependencies.yml
with:
node_version: '20.x'
+ with-front-dependencies-installation: true
synchronize-with-crowdin:
runs-on: ubuntu-latest
diff --git a/.github/workflows/crowdin_upload.yml b/.github/workflows/crowdin_upload.yml
index e35731f2..9ffb383b 100644
--- a/.github/workflows/crowdin_upload.yml
+++ b/.github/workflows/crowdin_upload.yml
@@ -7,13 +7,15 @@ on:
- main
jobs:
- install-front:
- uses: ./.github/workflows/front-dependencies-installation.yml
+ install-dependencies:
+ uses: ./.github/workflows/dependencies.yml
with:
node_version: '20.x'
+ with-front-dependencies-installation: true
+ with-build_mails: true
synchronize-with-crowdin:
- needs: install-front
+ needs: install-dependencies
runs-on: ubuntu-latest
steps:
@@ -29,6 +31,13 @@ jobs:
- name: Install development dependencies
run: pip install --user .
working-directory: src/backend
+ - name: Restore the mail templates
+ uses: actions/cache@v4
+ id: mail-templates
+ with:
+ path: "src/backend/core/templates/mail"
+ key: mail-templates-${{ hashFiles('src/mail/mjml') }}
+ fail-on-cache-miss: true
- name: Install gettext
run: |
sudo apt-get update
diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml
new file mode 100644
index 00000000..31097644
--- /dev/null
+++ b/.github/workflows/dependencies.yml
@@ -0,0 +1,85 @@
+name: Dependency reusable workflow
+
+on:
+ workflow_call:
+ inputs:
+ node_version:
+ required: false
+ default: '20.x'
+ type: string
+ with-front-dependencies-installation:
+ type: boolean
+ default: false
+ with-build_mails:
+ type: boolean
+ default: false
+
+jobs:
+ front-dependencies-installation:
+ if: ${{ inputs.with-front-dependencies-installation == true }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Restore the frontend cache
+ uses: actions/cache@v4
+ 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
+ with:
+ node-version: ${{ inputs.node_version }}
+ - name: Install dependencies
+ if: steps.front-node_modules.outputs.cache-hit != 'true'
+ 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
+ with:
+ path: "src/frontend/**/node_modules"
+ key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
+
+ build-mails:
+ if: ${{ inputs.with-build_mails == true }}
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: src/mail
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Restore the mail templates
+ uses: actions/cache@v4
+ id: mail-templates
+ with:
+ path: "src/backend/core/templates/mail"
+ key: mail-templates-${{ hashFiles('src/mail/mjml') }}
+
+ - name: Setup Node.js
+ if: steps.mail-templates.outputs.cache-hit != 'true'
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ inputs.node_version }}
+
+ - name: Install yarn
+ if: steps.mail-templates.outputs.cache-hit != 'true'
+ run: npm install -g yarn
+
+ - name: Install node dependencies
+ if: steps.mail-templates.outputs.cache-hit != 'true'
+ run: yarn install --frozen-lockfile
+
+ - name: Build mails
+ if: steps.mail-templates.outputs.cache-hit != 'true'
+ run: yarn build
+
+ - name: Cache mail templates
+ if: steps.mail-templates.outputs.cache-hit != 'true'
+ uses: actions/cache@v4
+ with:
+ path: "src/backend/core/templates/mail"
+ key: mail-templates-${{ hashFiles('src/mail/mjml') }}
diff --git a/.github/workflows/front-dependencies-installation.yml b/.github/workflows/front-dependencies-installation.yml
deleted file mode 100644
index 33bb3c43..00000000
--- a/.github/workflows/front-dependencies-installation.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Install frontend installation reusable workflow
-
-on:
- workflow_call:
- inputs:
- node_version:
- required: false
- default: '20.x'
- type: string
-
-jobs:
- front-dependencies-installation:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Restore the frontend cache
- uses: actions/cache@v4
- 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
- with:
- node-version: ${{ inputs.node_version }}
- - name: Install dependencies
- if: steps.front-node_modules.outputs.cache-hit != 'true'
- 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
- with:
- path: "src/frontend/**/node_modules"
- key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
diff --git a/.github/workflows/impress-frontend.yml b/.github/workflows/impress-frontend.yml
index c5d17c90..f869517d 100644
--- a/.github/workflows/impress-frontend.yml
+++ b/.github/workflows/impress-frontend.yml
@@ -10,13 +10,14 @@ on:
jobs:
- install-front:
- uses: ./.github/workflows/front-dependencies-installation.yml
+ install-dependencies:
+ uses: ./.github/workflows/dependencies.yml
with:
node_version: '20.x'
+ with-front-dependencies-installation: true
test-front:
- needs: install-front
+ needs: install-dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -39,7 +40,7 @@ jobs:
lint-front:
runs-on: ubuntu-latest
- needs: install-front
+ needs: install-dependencies
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -60,7 +61,7 @@ jobs:
test-e2e-chromium:
runs-on: ubuntu-latest
- needs: install-front
+ needs: install-dependencies
timeout-minutes: 20
steps:
- name: Checkout repository
diff --git a/.github/workflows/impress.yml b/.github/workflows/impress.yml
index 385aa633..97182a57 100644
--- a/.github/workflows/impress.yml
+++ b/.github/workflows/impress.yml
@@ -9,6 +9,11 @@ on:
- "*"
jobs:
+ install-dependencies:
+ uses: ./.github/workflows/dependencies.yml
+ with:
+ with-build_mails: true
+
lint-git:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' # Makes sense only for pull requests
@@ -56,46 +61,6 @@ jobs:
exit 1
fi
- build-mails:
- runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: src/mail
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "18"
-
- - name: Restore the mail templates
- uses: actions/cache@v4
- id: mail-templates
- with:
- path: "src/backend/core/templates/mail"
- key: mail-templates-${{ hashFiles('src/mail/mjml') }}
-
- - name: Install yarn
- if: steps.mail-templates.outputs.cache-hit != 'true'
- run: npm install -g yarn
-
- - name: Install node dependencies
- if: steps.mail-templates.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
-
- - name: Build mails
- if: steps.mail-templates.outputs.cache-hit != 'true'
- run: yarn build
-
- - name: Cache mail templates
- if: steps.mail-templates.outputs.cache-hit != 'true'
- uses: actions/cache@v4
- with:
- path: "src/backend/core/templates/mail"
- key: mail-templates-${{ hashFiles('src/mail/mjml') }}
-
lint-back:
runs-on: ubuntu-latest
defaults:
@@ -121,7 +86,7 @@ jobs:
test-back:
runs-on: ubuntu-latest
- needs: build-mails
+ needs: install-dependencies
defaults:
run:
@@ -169,6 +134,7 @@ jobs:
with:
path: "src/backend/core/templates/mail"
key: mail-templates-${{ hashFiles('src/mail/mjml') }}
+ fail-on-cache-miss: true
- name: Start MinIO
run: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a29f4cf8..0e07df1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,14 @@ and this project adheres to
## [Unreleased]
## Added
+
- 📝(doc) Add security.md and codeofconduct.md #604
+- ✨(frontend) add home page #553
+
+
+## Fixed
+
+🌐(CI) Fix email partially translated #616
## [2.1.0] - 2025-01-29
diff --git a/src/backend/core/tests/documents/test_api_document_invitations.py b/src/backend/core/tests/documents/test_api_document_invitations.py
index 5a5e40f7..c901f276 100644
--- a/src/backend/core/tests/documents/test_api_document_invitations.py
+++ b/src/backend/core/tests/documents/test_api_document_invitations.py
@@ -458,6 +458,10 @@ def test_api_document_invitations_create_email_from_content_language():
email_content = " ".join(email.body.split())
assert f"{user.full_name} a partagé un document avec vous!" in email_content
+ assert (
+ "Docs, votre nouvel outil incontournable pour organiser, partager et collaborer "
+ "sur vos documents en équipe." in email_content
+ )
def test_api_document_invitations_create_email_from_content_language_not_supported():
diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po
index 1e608dd4..1d2b67f5 100644
--- a/src/backend/locale/de_DE/LC_MESSAGES/django.po
+++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-01-29 13:43+0000\n"
-"PO-Revision-Date: 2025-01-30 10:24\n"
+"POT-Creation-Date: 2025-02-06 15:30+0000\n"
+"PO-Revision-Date: 2025-02-06 15:59\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -391,3 +391,24 @@ msgstr "Französisch"
msgid "German"
msgstr "Deutsch"
+#: core/templates/mail/html/invitation.html:162
+#: core/templates/mail/text/invitation.txt:3
+msgid "Logo email"
+msgstr "Logo-E-Mail"
+
+#: core/templates/mail/html/invitation.html:209
+#: core/templates/mail/text/invitation.txt:10
+msgid "Open"
+msgstr "Öffnen"
+
+#: core/templates/mail/html/invitation.html:226
+#: core/templates/mail/text/invitation.txt:14
+msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
+msgstr " Docs, Ihr neues unentbehrliches Werkzeug für die Organisation, den Austausch und die Zusammenarbeit in Ihren Dokumenten als Team. "
+
+#: core/templates/mail/html/invitation.html:233
+#: core/templates/mail/text/invitation.txt:16
+#, python-format
+msgid " Brought to you by %(brandname)s "
+msgstr " Erstellt von %(brandname)s "
+
diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po
index 525e28d3..05c67e91 100644
--- a/src/backend/locale/en_US/LC_MESSAGES/django.po
+++ b/src/backend/locale/en_US/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-01-29 13:43+0000\n"
-"PO-Revision-Date: 2025-01-30 10:24\n"
+"POT-Creation-Date: 2025-02-06 15:30+0000\n"
+"PO-Revision-Date: 2025-02-06 15:57\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -391,3 +391,24 @@ msgstr ""
msgid "German"
msgstr ""
+#: core/templates/mail/html/invitation.html:162
+#: core/templates/mail/text/invitation.txt:3
+msgid "Logo email"
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:209
+#: core/templates/mail/text/invitation.txt:10
+msgid "Open"
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:226
+#: core/templates/mail/text/invitation.txt:14
+msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:233
+#: core/templates/mail/text/invitation.txt:16
+#, python-format
+msgid " Brought to you by %(brandname)s "
+msgstr ""
+
diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
index beea0690..927fece3 100644
--- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po
+++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-01-29 13:43+0000\n"
-"PO-Revision-Date: 2025-01-30 10:24\n"
+"POT-Creation-Date: 2025-02-06 15:30+0000\n"
+"PO-Revision-Date: 2025-02-06 15:59\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -391,3 +391,24 @@ msgstr ""
msgid "German"
msgstr ""
+#: core/templates/mail/html/invitation.html:162
+#: core/templates/mail/text/invitation.txt:3
+msgid "Logo email"
+msgstr "Logo de l'e-mail"
+
+#: core/templates/mail/html/invitation.html:209
+#: core/templates/mail/text/invitation.txt:10
+msgid "Open"
+msgstr "Ouvrir"
+
+#: core/templates/mail/html/invitation.html:226
+#: core/templates/mail/text/invitation.txt:14
+msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
+msgstr " Docs, votre nouvel outil incontournable pour organiser, partager et collaborer sur vos documents en équipe. "
+
+#: core/templates/mail/html/invitation.html:233
+#: core/templates/mail/text/invitation.txt:16
+#, python-format
+msgid " Brought to you by %(brandname)s "
+msgstr " Proposé par %(brandname)s "
+
diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
index 79b4f0b1..b4506cdf 100644
--- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po
+++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-01-29 13:43+0000\n"
-"PO-Revision-Date: 2025-01-30 10:24\n"
+"POT-Creation-Date: 2025-02-06 15:30+0000\n"
+"PO-Revision-Date: 2025-02-06 15:57\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -391,3 +391,24 @@ msgstr ""
msgid "German"
msgstr ""
+#: core/templates/mail/html/invitation.html:162
+#: core/templates/mail/text/invitation.txt:3
+msgid "Logo email"
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:209
+#: core/templates/mail/text/invitation.txt:10
+msgid "Open"
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:226
+#: core/templates/mail/text/invitation.txt:14
+msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
+msgstr ""
+
+#: core/templates/mail/html/invitation.html:233
+#: core/templates/mail/text/invitation.txt:16
+#, python-format
+msgid " Brought to you by %(brandname)s "
+msgstr ""
+
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts
index 6e65dc65..31de3f4a 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts
@@ -1,6 +1,17 @@
import { Page, expect } from '@playwright/test';
-export const keyCloakSignIn = async (page: Page, browserName: string) => {
+export const keyCloakSignIn = async (
+ page: Page,
+ browserName: string,
+ fromHome: boolean = true,
+) => {
+ if (fromHome) {
+ await page
+ .getByRole('button', { name: 'Proconnect Login' })
+ .first()
+ .click();
+ }
+
const login = `user-e2e-${browserName}`;
const password = `password-e2e-${browserName}`;
@@ -258,3 +269,8 @@ export const mockedAccesses = async (page: Page, json?: object) => {
}
});
};
+
+export const expectLoginPage = async (page: Page) =>
+ await expect(
+ page.getByRole('heading', { name: 'Collaborative writing' }),
+ ).toBeVisible();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
index 042c6287..7c9639ba 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
@@ -63,27 +63,6 @@ test.describe('Config', () => {
expect((await consoleMessage).text()).toContain(invalidMsg);
});
- test('it checks that theme is configured from config endpoint', async ({
- page,
- }) => {
- const responsePromise = page.waitForResponse(
- (response) =>
- response.url().includes('/config/') && response.status() === 200,
- );
-
- await page.goto('/');
-
- const response = await responsePromise;
- expect(response.ok()).toBeTruthy();
-
- const jsonResponse = await response.json();
- expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr');
-
- const footer = page.locator('footer').first();
- // alt 'Gouvernement Logo' comes from the theme
- await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
- });
-
test('it checks that media server is configured from config endpoint', async ({
page,
browserName,
@@ -161,3 +140,28 @@ test.describe('Config', () => {
).toBeVisible();
});
});
+
+test.describe('Config: Not loggued', () => {
+ test.use({ storageState: { cookies: [], origins: [] } });
+
+ test('it checks that theme is configured from config endpoint', async ({
+ page,
+ }) => {
+ const responsePromise = page.waitForResponse(
+ (response) =>
+ response.url().includes('/config/') && response.status() === 200,
+ );
+
+ await page.goto('/');
+
+ const response = await responsePromise;
+ expect(response.ok()).toBeTruthy();
+
+ const jsonResponse = await response.json();
+ expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr');
+
+ const footer = page.locator('footer').first();
+ // alt 'Gouvernement Logo' comes from the theme
+ await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
+ });
+});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
index 3fbd1d08..52aedce0 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
@@ -24,8 +24,6 @@ test.describe('Doc Create', () => {
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
- await expect(page.getByTestId('grid-loader')).toBeVisible();
-
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
index 772ec104..31cee30d 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
@@ -213,7 +213,6 @@ test.describe('Document grid item options', () => {
test.describe('Documents filters', () => {
test('it checks the prebuild left panel filters', async ({ page }) => {
// All Docs
- await expect(page.getByTestId('grid-loader')).toBeVisible();
const response = await page.waitForResponse(
(response) =>
response.url().endsWith('documents/?page=1') &&
@@ -254,7 +253,6 @@ test.describe('Documents filters', () => {
url = new URL(page.url());
target = url.searchParams.get('target');
expect(target).toBe('my_docs');
- await expect(page.getByTestId('grid-loader')).toBeVisible();
const responseMyDocs = await page.waitForResponse(
(response) =>
response.url().endsWith('documents/?page=1&is_creator_me=true') &&
@@ -270,7 +268,6 @@ test.describe('Documents filters', () => {
url = new URL(page.url());
target = url.searchParams.get('target');
expect(target).toBe('shared_with_me');
- await expect(page.getByTestId('grid-loader')).toBeVisible();
const responseSharedWithMe = await page.waitForResponse(
(response) =>
response.url().includes('documents/?page=1&is_creator_me=false') &&
@@ -291,8 +288,6 @@ test.describe('Documents Grid', () => {
test('checks all the elements are visible', async ({ page }) => {
let docs: SmallDoc[] = [];
- await expect(page.getByTestId('grid-loader')).toBeVisible();
-
const response = await page.waitForResponse(
(response) =>
response.url().endsWith('documents/?page=1') &&
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts
index 4a080dee..5280a892 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts
@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
-import { keyCloakSignIn, mockedDocument } from './common';
+import { expectLoginPage, keyCloakSignIn, mockedDocument } from './common';
test.describe('Doc Routing', () => {
test.beforeEach(async ({ page }) => {
@@ -63,16 +63,13 @@ test.describe('Doc Routing: Not loggued', () => {
await page.goto('/docs/mocked-document-id/');
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
await page.getByRole('button', { name: 'Login' }).click();
- await keyCloakSignIn(page, browserName);
+ await keyCloakSignIn(page, browserName, false);
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
});
+ // eslint-disable-next-line playwright/expect-expect
test('The homepage redirects to login.', async ({ page }) => {
await page.goto('/');
- await expect(
- page.getByRole('button', {
- name: 'Sign In',
- }),
- ).toBeVisible();
+ await expectLoginPage(page);
});
});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts
index dc7fb7eb..e4ee37f4 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts
@@ -1,6 +1,11 @@
import { expect, test } from '@playwright/test';
-import { createDoc, keyCloakSignIn, verifyDocName } from './common';
+import {
+ createDoc,
+ expectLoginPage,
+ keyCloakSignIn,
+ verifyDocName,
+} from './common';
const browsersName = ['chromium', 'webkit', 'firefox'];
@@ -91,7 +96,7 @@ test.describe('Doc Visibility: Restricted', () => {
})
.click();
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
+ await expectLoginPage(page);
await page.goto(urlDoc);
@@ -121,6 +126,10 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
+ await expect(
+ page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
+ ).toBeVisible();
+
await page.goto(urlDoc);
await expect(
@@ -169,10 +178,11 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
- await page.goto(urlDoc);
+ await expect(
+ page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
+ ).toBeVisible();
- // eslint-disable-next-line playwright/no-wait-for-timeout
- await page.waitForTimeout(1000);
+ await page.goto(urlDoc);
await verifyDocName(page, docTitle);
await expect(page.getByLabel('Share button')).toBeVisible();
@@ -247,7 +257,7 @@ test.describe('Doc Visibility: Public', () => {
})
.click();
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
+ await expectLoginPage(page);
await page.goto(urlDoc);
@@ -313,7 +323,7 @@ test.describe('Doc Visibility: Public', () => {
})
.click();
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
+ await expectLoginPage(page);
await page.goto(urlDoc);
@@ -364,7 +374,7 @@ test.describe('Doc Visibility: Authenticated', () => {
})
.click();
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
+ await expectLoginPage(page);
await page.goto(urlDoc);
@@ -414,6 +424,10 @@ test.describe('Doc Visibility: Authenticated', () => {
const otherBrowser = browsersName.find((b) => b !== browserName);
await keyCloakSignIn(page, otherBrowser!);
+ await expect(
+ page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
+ ).toBeVisible();
+
await page.goto(urlDoc);
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
@@ -470,6 +484,10 @@ test.describe('Doc Visibility: Authenticated', () => {
const otherBrowser = browsersName.find((b) => b !== browserName);
await keyCloakSignIn(page, otherBrowser!);
+ await expect(
+ page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
+ ).toBeVisible();
+
await page.goto(urlDoc);
await verifyDocName(page, docTitle);
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts
index 0862051f..a1878ccb 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/footer.spec.ts
@@ -1,13 +1,10 @@
import { expect, test } from '@playwright/test';
-import { goToGridDoc } from './common';
-
-test.beforeEach(async ({ page }) => {
- await page.goto('/');
-});
-
test.describe('Footer', () => {
+ test.use({ storageState: { cookies: [], origins: [] } });
+
test('checks all the elements are visible', async ({ page }) => {
+ await page.goto('/');
const footer = page.locator('footer').first();
await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
@@ -47,12 +44,6 @@ test.describe('Footer', () => {
).toBeVisible();
});
- test('checks footer is not visible on doc editor', async ({ page }) => {
- await expect(page.locator('footer')).toBeVisible();
- await goToGridDoc(page);
- await expect(page.locator('footer')).toBeHidden();
- });
-
const legalPages = [
{ name: 'Legal Notice', url: '/legal-notice/' },
{ name: 'Personal data and cookies', url: '/personal-data-cookies/' },
@@ -60,6 +51,8 @@ test.describe('Footer', () => {
];
for (const { name, url } of legalPages) {
test(`checks ${name} page`, async ({ page }) => {
+ await page.goto('/');
+
const footer = page.locator('footer').first();
await footer.getByRole('link', { name }).click();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
index 5ef24c46..795d04cf 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/header.spec.ts
@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
-import { keyCloakSignIn } from './common';
+import { expectLoginPage, keyCloakSignIn } from './common';
test.describe('Header', () => {
test.beforeEach(async ({ page }) => {
@@ -98,6 +98,6 @@ test.describe('Header: Log out', () => {
})
.click();
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
+ await expectLoginPage(page);
});
});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts
new file mode 100644
index 00000000..8f09c346
--- /dev/null
+++ b/src/frontend/apps/e2e/__tests__/app-impress/home.spec.ts
@@ -0,0 +1,52 @@
+import { expect, test } from '@playwright/test';
+
+test.beforeEach(async ({ page }) => {
+ await page.goto('/docs/');
+});
+
+test.describe('Home page', () => {
+ test.use({ storageState: { cookies: [], origins: [] } });
+ test('checks all the elements are visible', async ({ page }) => {
+ // Check header content
+ const header = page.locator('header').first();
+ const footer = page.locator('footer').first();
+ await expect(header).toBeVisible();
+ await expect(
+ header.getByRole('combobox', { name: 'Language' }),
+ ).toBeVisible();
+ await expect(
+ header.getByRole('button', { name: 'Les services de La Suite numé' }),
+ ).toBeVisible();
+ await expect(
+ header.getByRole('img', { name: 'Gouvernement Logo' }),
+ ).toBeVisible();
+ await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible();
+ await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
+ await expect(header.getByText('BETA')).toBeVisible();
+
+ // Check the titles
+ const h2 = page.locator('h2');
+ await expect(
+ h2.getByText('Collaborative writing, Simplified.'),
+ ).toBeVisible();
+ await expect(
+ h2.getByText('An uncompromising writing experience.'),
+ ).toBeVisible();
+ await expect(
+ h2.getByText('Simple and secure collaboration.'),
+ ).toBeVisible();
+ await expect(h2.getByText('Flexible export.')).toBeVisible();
+ await expect(
+ h2.getByText('A new way to organize knowledge.'),
+ ).toBeVisible();
+
+ await expect(
+ page.getByText('Docs is already available, log in to use it now.'),
+ ).toBeVisible();
+ await expect(
+ page.getByRole('button', { name: 'Proconnect Login' }),
+ ).toHaveCount(2);
+
+ await expect(footer).toBeVisible();
+ });
+});
diff --git a/src/frontend/apps/impress/cunningham.ts b/src/frontend/apps/impress/cunningham.ts
index a324b170..3a96c054 100644
--- a/src/frontend/apps/impress/cunningham.ts
+++ b/src/frontend/apps/impress/cunningham.ts
@@ -5,6 +5,7 @@ const config = {
colors: {
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
+ 'primary-action': '#1212FF',
'primary-050': '#F5F5FE',
'primary-100': '#EDF5FA',
'primary-150': '#E5EEFA',
@@ -59,6 +60,11 @@ const config = {
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,
@@ -224,7 +230,7 @@ const config = {
'color-hover': 'var(--c--theme--colors--primary-700)',
},
border: {
- color: 'var(--c--theme--colors--primary-200)',
+ color: 'var(--c--theme--colors--greyscale-300)',
},
},
tertiary: {
@@ -247,6 +253,9 @@ const config = {
'la-gauffre': {
activated: false,
},
+ 'home-proconnect': {
+ activated: false,
+ },
},
},
dsfr: {
@@ -379,8 +388,8 @@ const config = {
'color-active': '#EDEDED',
},
border: {
- color: 'var(--c--theme--colors--primary-600)',
- 'color-hover': 'var(--c--theme--colors--primary-600)',
+ color: 'var(--c--theme--colors--greyscale-300)',
+ 'color-hover': 'var(--c--theme--colors--greyscale-300)',
},
color: 'var(--c--theme--colors--primary-text)',
},
@@ -462,6 +471,9 @@ const config = {
'la-gauffre': {
activated: true,
},
+ 'home-proconnect': {
+ activated: true,
+ },
},
},
},
diff --git a/src/frontend/apps/impress/public/assets/SC1-en.webm b/src/frontend/apps/impress/public/assets/SC1-en.webm
new file mode 100644
index 00000000..ff1dc284
Binary files /dev/null and b/src/frontend/apps/impress/public/assets/SC1-en.webm differ
diff --git a/src/frontend/apps/impress/public/assets/SC1-fr.webm b/src/frontend/apps/impress/public/assets/SC1-fr.webm
new file mode 100644
index 00000000..1ebc7bef
Binary files /dev/null and b/src/frontend/apps/impress/public/assets/SC1-fr.webm differ
diff --git a/src/frontend/apps/impress/public/assets/logo-gouv.svg b/src/frontend/apps/impress/public/assets/logo-gouv.svg
index 583adb17..824e1b94 100644
--- a/src/frontend/apps/impress/public/assets/logo-gouv.svg
+++ b/src/frontend/apps/impress/public/assets/logo-gouv.svg
@@ -1,241 +1,454 @@
-
diff --git a/src/frontend/apps/impress/public/favicon.ico b/src/frontend/apps/impress/public/favicon.ico
index 5436f34d..97630272 100644
Binary files a/src/frontend/apps/impress/public/favicon.ico and b/src/frontend/apps/impress/public/favicon.ico differ
diff --git a/src/frontend/apps/impress/src/assets/icons/icon-docs.svg b/src/frontend/apps/impress/src/assets/icons/icon-docs.svg
new file mode 100644
index 00000000..430eb0e0
--- /dev/null
+++ b/src/frontend/apps/impress/src/assets/icons/icon-docs.svg
@@ -0,0 +1,18 @@
+
diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx
index 39a2f962..52c227e7 100644
--- a/src/frontend/apps/impress/src/core/AppProvider.tsx
+++ b/src/frontend/apps/impress/src/core/AppProvider.tsx
@@ -3,10 +3,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useCunninghamTheme } from '@/cunningham';
+import { Auth } from '@/features/auth';
import '@/i18n/initI18n';
import { useResponsiveStore } from '@/stores/';
-import { Auth } from './auth/';
import { ConfigProvider } from './config/';
/**
diff --git a/src/frontend/apps/impress/src/core/auth/Auth.tsx b/src/frontend/apps/impress/src/core/auth/Auth.tsx
deleted file mode 100644
index bcfa6a53..00000000
--- a/src/frontend/apps/impress/src/core/auth/Auth.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Loader } from '@openfun/cunningham-react';
-import { useRouter } from 'next/router';
-import { PropsWithChildren, useEffect, useState } from 'react';
-
-import { Box } from '@/components';
-
-import { useAuthStore } from './useAuthStore';
-
-/**
- * TODO: Remove this restriction when we will have a homepage design for non-authenticated users.
- *
- * We define the paths that are not allowed without authentication.
- * Actually, only the home page and the docs page are not allowed without authentication.
- * When we will have a homepage design for non-authenticated users, we will remove this restriction to have
- * the full website accessible without authentication.
- */
-const regexpUrlsAuth = [/\/docs\/$/g, /^\/$/g];
-
-export const Auth = ({ children }: PropsWithChildren) => {
- const { initAuth, initiated, authenticated, login, getAuthUrl } =
- useAuthStore();
- const { asPath, replace } = useRouter();
-
- const [pathAllowed, setPathAllowed] = useState(
- !regexpUrlsAuth.some((regexp) => !!asPath.match(regexp)),
- );
-
- useEffect(() => {
- initAuth();
- }, [initAuth]);
-
- useEffect(() => {
- setPathAllowed(!regexpUrlsAuth.some((regexp) => !!asPath.match(regexp)));
- }, [asPath]);
-
- // We force to login except on allowed paths
- useEffect(() => {
- if (!initiated || authenticated || pathAllowed) {
- return;
- }
-
- login();
- }, [authenticated, pathAllowed, login, initiated]);
-
- // Redirect to the path before login
- useEffect(() => {
- if (!authenticated) {
- return;
- }
-
- const authUrl = getAuthUrl();
- if (authUrl) {
- void replace(authUrl);
- }
- }, [authenticated, getAuthUrl, replace]);
-
- if ((!initiated && pathAllowed) || (!authenticated && !pathAllowed)) {
- return (
-
-
-
- );
- }
-
- return children;
-};
diff --git a/src/frontend/apps/impress/src/core/auth/ButtonLogin.tsx b/src/frontend/apps/impress/src/core/auth/ButtonLogin.tsx
deleted file mode 100644
index abbdc437..00000000
--- a/src/frontend/apps/impress/src/core/auth/ButtonLogin.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Button } from '@openfun/cunningham-react';
-import { useTranslation } from 'react-i18next';
-
-import { useAuthStore } from '@/core/auth';
-
-export const ButtonLogin = () => {
- const { t } = useTranslation();
- const { logout, authenticated, login } = useAuthStore();
-
- if (!authenticated) {
- return (
-
- );
- }
-
- return (
-
- );
-};
diff --git a/src/frontend/apps/impress/src/core/auth/api/index.ts b/src/frontend/apps/impress/src/core/auth/api/index.ts
deleted file mode 100644
index d81520d1..00000000
--- a/src/frontend/apps/impress/src/core/auth/api/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './getMe';
-export * from './types';
diff --git a/src/frontend/apps/impress/src/core/auth/conf.ts b/src/frontend/apps/impress/src/core/auth/conf.ts
deleted file mode 100644
index 63a3701f..00000000
--- a/src/frontend/apps/impress/src/core/auth/conf.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth';
diff --git a/src/frontend/apps/impress/src/core/auth/index.ts b/src/frontend/apps/impress/src/core/auth/index.ts
deleted file mode 100644
index e45c9a64..00000000
--- a/src/frontend/apps/impress/src/core/auth/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from './api/types';
-export * from './Auth';
-export * from './ButtonLogin';
-export * from './useAuthStore';
diff --git a/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx b/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx
deleted file mode 100644
index 63e285b2..00000000
--- a/src/frontend/apps/impress/src/core/auth/useAuthStore.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { create } from 'zustand';
-
-import { baseApiUrl } from '@/api';
-import { terminateCrispSession } from '@/services';
-
-import { User, getMe } from './api';
-import { PATH_AUTH_LOCAL_STORAGE } from './conf';
-
-interface AuthStore {
- initiated: boolean;
- authenticated: boolean;
- initAuth: () => void;
- logout: () => void;
- login: () => void;
- setAuthUrl: (url: string) => void;
- getAuthUrl: () => string | undefined;
- userData?: User;
-}
-
-const initialState = {
- initiated: false,
- authenticated: false,
- userData: undefined,
-};
-
-export const useAuthStore = create((set, get) => ({
- initiated: initialState.initiated,
- authenticated: initialState.authenticated,
- userData: initialState.userData,
- initAuth: () => {
- getMe()
- .then((data: User) => {
- set({ authenticated: true, userData: data });
- })
- .catch(() => {})
- .finally(() => {
- set({ initiated: true });
- });
- },
- login: () => {
- get().setAuthUrl(window.location.pathname);
-
- window.location.replace(`${baseApiUrl()}authenticate/`);
- },
- logout: () => {
- terminateCrispSession();
- window.location.replace(`${baseApiUrl()}logout/`);
- },
- // If we try to access a specific page and we are not authenticated
- // we store the path in the local storage to redirect to it after login
- setAuthUrl() {
- if (window.location.pathname !== '/') {
- localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.pathname);
- }
- },
- // If a path is stored in the local storage, we return it then remove it
- getAuthUrl() {
- const path_auth = localStorage.getItem(PATH_AUTH_LOCAL_STORAGE);
- if (path_auth) {
- localStorage.removeItem(PATH_AUTH_LOCAL_STORAGE);
- return path_auth;
- }
- },
-}));
diff --git a/src/frontend/apps/impress/src/core/index.ts b/src/frontend/apps/impress/src/core/index.ts
index 20df7a86..831279fd 100644
--- a/src/frontend/apps/impress/src/core/index.ts
+++ b/src/frontend/apps/impress/src/core/index.ts
@@ -1,3 +1,2 @@
export * from './AppProvider';
-export * from './auth';
export * from './config';
diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
index 3357c29c..d3af0dec 100644
--- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
+++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.css
@@ -71,6 +71,7 @@
--c--theme--colors--danger-text: var(--c--theme--colors--greyscale-000);
--c--theme--colors--card-border: #ededed;
--c--theme--colors--primary-bg: #fafafa;
+ --c--theme--colors--primary-action: #1212ff;
--c--theme--colors--primary-050: #f5f5fe;
--c--theme--colors--primary-150: #e5eefa;
--c--theme--colors--primary-950: #1b1b35;
@@ -122,6 +123,11 @@
--c--theme--font--sizes--ml: 0.938rem;
--c--theme--font--sizes--xl: 1.25rem;
--c--theme--font--sizes--t: 0.6875rem;
+ --c--theme--font--sizes--xl-alt: 5rem;
+ --c--theme--font--sizes--lg-alt: 4.5rem;
+ --c--theme--font--sizes--md-alt: 4rem;
+ --c--theme--font--sizes--sm-alt: 3.5rem;
+ --c--theme--font--sizes--xs-alt: 3rem;
--c--theme--font--weights--thin: 100;
--c--theme--font--weights--light: 300;
--c--theme--font--weights--regular: 400;
@@ -316,7 +322,7 @@
--c--theme--colors--primary-700
);
--c--components--button--secondary--border--color: var(
- --c--theme--colors--primary-200
+ --c--theme--colors--greyscale-300
);
--c--components--button--tertiary--color: var(
--c--theme--colors--primary-text
@@ -339,6 +345,7 @@
--c--components--button--disabled--color: white;
--c--components--button--disabled--background--color: #b3cef0;
--c--components--la-gauffre--activated: false;
+ --c--components--home-proconnect--activated: false;
}
.cunningham-theme--dark {
@@ -501,10 +508,10 @@
--c--components--button--secondary--background--color-hover: #f6f6f6;
--c--components--button--secondary--background--color-active: #ededed;
--c--components--button--secondary--border--color: var(
- --c--theme--colors--primary-600
+ --c--theme--colors--greyscale-300
);
--c--components--button--secondary--border--color-hover: var(
- --c--theme--colors--primary-600
+ --c--theme--colors--greyscale-300
);
--c--components--button--secondary--color: var(
--c--theme--colors--primary-text
@@ -584,6 +591,7 @@
);
--c--components--forms-textarea--border-radius: 0;
--c--components--la-gauffre--activated: true;
+ --c--components--home-proconnect--activated: true;
}
.clr-secondary-text {
@@ -874,6 +882,10 @@
color: var(--c--theme--colors--primary-bg);
}
+.clr-primary-action {
+ color: var(--c--theme--colors--primary-action);
+}
+
.clr-primary-050 {
color: var(--c--theme--colors--primary-050);
}
@@ -1302,6 +1314,10 @@
background-color: var(--c--theme--colors--primary-bg);
}
+.bg-primary-action {
+ background-color: var(--c--theme--colors--primary-action);
+}
+
.bg-primary-050 {
background-color: var(--c--theme--colors--primary-050);
}
@@ -1550,6 +1566,31 @@
letter-spacing: var(--c--theme--font--letterspacings--t);
}
+.fs-xl-alt {
+ font-size: var(--c--theme--font--sizes--xl-alt);
+ letter-spacing: var(--c--theme--font--letterspacings--xl-alt);
+}
+
+.fs-lg-alt {
+ font-size: var(--c--theme--font--sizes--lg-alt);
+ letter-spacing: var(--c--theme--font--letterspacings--lg-alt);
+}
+
+.fs-md-alt {
+ font-size: var(--c--theme--font--sizes--md-alt);
+ letter-spacing: var(--c--theme--font--letterspacings--md-alt);
+}
+
+.fs-sm-alt {
+ font-size: var(--c--theme--font--sizes--sm-alt);
+ letter-spacing: var(--c--theme--font--letterspacings--sm-alt);
+}
+
+.fs-xs-alt {
+ font-size: var(--c--theme--font--sizes--xs-alt);
+ letter-spacing: var(--c--theme--font--letterspacings--xs-alt);
+}
+
.f-base {
font-family: var(--c--theme--font--families--base);
}
diff --git a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
index 521094f4..9c38dcd8 100644
--- a/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
+++ b/src/frontend/apps/impress/src/cunningham/cunningham-tokens.ts
@@ -75,6 +75,7 @@ export const tokens = {
'danger-text': '#fff',
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
+ 'primary-action': '#1212FF',
'primary-050': '#F5F5FE',
'primary-150': '#E5EEFA',
'primary-950': '#1B1B35',
@@ -129,6 +130,11 @@ export const tokens = {
ml: '0.938rem',
xl: '1.25rem',
t: '0.6875rem',
+ 'xl-alt': '5rem',
+ 'lg-alt': '4.5rem',
+ 'md-alt': '4rem',
+ 'sm-alt': '3.5rem',
+ 'xs-alt': '3rem',
},
weights: {
thin: 100,
@@ -315,7 +321,7 @@ export const tokens = {
color: 'white',
'color-hover': 'var(--c--theme--colors--primary-700)',
},
- border: { color: 'var(--c--theme--colors--primary-200)' },
+ border: { color: 'var(--c--theme--colors--greyscale-300)' },
},
tertiary: {
color: 'var(--c--theme--colors--primary-text)',
@@ -330,6 +336,7 @@ export const tokens = {
disabled: { color: 'white', background: { color: '#b3cef0' } },
},
'la-gauffre': { activated: false },
+ 'home-proconnect': { activated: false },
},
},
dark: {
@@ -502,8 +509,8 @@ export const tokens = {
secondary: {
background: { 'color-hover': '#F6F6F6', 'color-active': '#EDEDED' },
border: {
- color: 'var(--c--theme--colors--primary-600)',
- 'color-hover': 'var(--c--theme--colors--primary-600)',
+ color: 'var(--c--theme--colors--greyscale-300)',
+ 'color-hover': 'var(--c--theme--colors--greyscale-300)',
},
color: 'var(--c--theme--colors--primary-text)',
},
@@ -575,6 +582,7 @@ export const tokens = {
},
'forms-textarea': { 'border-radius': '0' },
'la-gauffre': { activated: true },
+ 'home-proconnect': { activated: true },
},
},
},
diff --git a/src/frontend/apps/impress/src/core/auth/__tests__/useAuthStore.test.tsx b/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx
similarity index 86%
rename from src/frontend/apps/impress/src/core/auth/__tests__/useAuthStore.test.tsx
rename to src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx
index 02814677..2aa0a02c 100644
--- a/src/frontend/apps/impress/src/core/auth/__tests__/useAuthStore.test.tsx
+++ b/src/frontend/apps/impress/src/features/auth/__tests__/utils.test.tsx
@@ -1,7 +1,7 @@
import { Crisp } from 'crisp-sdk-web';
import fetchMock from 'fetch-mock';
-import { useAuthStore } from '../useAuthStore';
+import { gotoLogout } from '../utils';
jest.mock('crisp-sdk-web', () => ({
...jest.requireActual('crisp-sdk-web'),
@@ -17,7 +17,7 @@ jest.mock('crisp-sdk-web', () => ({
},
}));
-describe('useAuthStore', () => {
+describe('utils', () => {
afterEach(() => {
jest.clearAllMocks();
fetchMock.restore();
@@ -33,7 +33,7 @@ describe('useAuthStore', () => {
writable: true,
});
- useAuthStore.getState().logout();
+ gotoLogout();
expect(Crisp.session.reset).toHaveBeenCalled();
});
diff --git a/src/frontend/apps/impress/src/features/auth/api/index.ts b/src/frontend/apps/impress/src/features/auth/api/index.ts
new file mode 100644
index 00000000..ce8db5d4
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/api/index.ts
@@ -0,0 +1,2 @@
+export * from './useAuthQuery';
+export * from './types';
diff --git a/src/frontend/apps/impress/src/core/auth/api/types.ts b/src/frontend/apps/impress/src/features/auth/api/types.ts
similarity index 100%
rename from src/frontend/apps/impress/src/core/auth/api/types.ts
rename to src/frontend/apps/impress/src/features/auth/api/types.ts
diff --git a/src/frontend/apps/impress/src/core/auth/api/getMe.tsx b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
similarity index 61%
rename from src/frontend/apps/impress/src/core/auth/api/getMe.tsx
rename to src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
index 4c02c27b..714cc9e0 100644
--- a/src/frontend/apps/impress/src/core/auth/api/getMe.tsx
+++ b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
@@ -1,4 +1,6 @@
-import { fetchAPI } from '@/api';
+import { UseQueryOptions, useQuery } from '@tanstack/react-query';
+
+import { APIError, fetchAPI } from '@/api';
import { User } from './types';
@@ -19,3 +21,16 @@ export const getMe = async (): Promise => {
}
return response.json() as Promise;
};
+
+export const KEY_AUTH = 'auth';
+
+export function useAuthQuery(
+ queryConfig?: UseQueryOptions,
+) {
+ return useQuery({
+ queryKey: [KEY_AUTH],
+ queryFn: getMe,
+ staleTime: 1000 * 60 * 15, // 15 minutes
+ ...queryConfig,
+ });
+}
diff --git a/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg b/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg
new file mode 100644
index 00000000..10d1f499
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/assets/button-proconnect.svg
@@ -0,0 +1,40 @@
+
diff --git a/src/frontend/apps/impress/src/features/auth/components/Auth.tsx b/src/frontend/apps/impress/src/features/auth/components/Auth.tsx
new file mode 100644
index 00000000..73728786
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/components/Auth.tsx
@@ -0,0 +1,47 @@
+import { Loader } from '@openfun/cunningham-react';
+import { useRouter } from 'next/router';
+import { PropsWithChildren } from 'react';
+
+import { Box } from '@/components';
+
+import { useAuth } from '../hooks';
+
+export const Auth = ({ children }: PropsWithChildren) => {
+ const { isLoading, pathAllowed, isFetchedAfterMount, authenticated } =
+ useAuth();
+ const { replace, pathname } = useRouter();
+
+ if (isLoading && !isFetchedAfterMount) {
+ return (
+
+
+
+ );
+ }
+
+ /**
+ * If the user is not authenticated and the path is not allowed, we redirect to the login page.
+ */
+ if (!authenticated && !pathAllowed) {
+ void replace('/login');
+ return (
+
+
+
+ );
+ }
+
+ /**
+ * If the user is authenticated and the path is the login page, we redirect to the home page.
+ */
+ if (pathname === '/login' && authenticated) {
+ void replace('/');
+ return (
+
+
+
+ );
+ }
+
+ return children;
+};
diff --git a/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx
new file mode 100644
index 00000000..a96f3555
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/components/ButtonLogin.tsx
@@ -0,0 +1,48 @@
+import { Button } from '@openfun/cunningham-react';
+import Image from 'next/image';
+import { useTranslation } from 'react-i18next';
+import { css } from 'styled-components';
+
+import { BoxButton } from '@/components';
+
+import ProConnectImg from '../assets/button-proconnect.svg?url';
+import { useAuth } from '../hooks';
+import { gotoLogin, gotoLogout } from '../utils';
+
+export const ButtonLogin = () => {
+ const { t } = useTranslation();
+ const { authenticated } = useAuth();
+
+ if (!authenticated) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export const ProConnectButton = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+};
diff --git a/src/frontend/apps/impress/src/features/auth/components/index.ts b/src/frontend/apps/impress/src/features/auth/components/index.ts
new file mode 100644
index 00000000..17f3a905
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/components/index.ts
@@ -0,0 +1,2 @@
+export * from './Auth';
+export * from './ButtonLogin';
diff --git a/src/frontend/apps/impress/src/features/auth/conf.ts b/src/frontend/apps/impress/src/features/auth/conf.ts
new file mode 100644
index 00000000..b8e59cf5
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/conf.ts
@@ -0,0 +1,5 @@
+import { baseApiUrl } from '@/api';
+
+export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth';
+export const LOGIN_URL = `${baseApiUrl()}authenticate/`;
+export const LOGOUT_URL = `${baseApiUrl()}logout/`;
diff --git a/src/frontend/apps/impress/src/features/auth/hooks/index.ts b/src/frontend/apps/impress/src/features/auth/hooks/index.ts
new file mode 100644
index 00000000..d9ae7204
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useAuth';
diff --git a/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx b/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx
new file mode 100644
index 00000000..6a13d514
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/hooks/useAuth.tsx
@@ -0,0 +1,34 @@
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+
+import { useAuthQuery } from '../api';
+import { getAuthUrl } from '../utils';
+
+const regexpUrlsAuth = [/\/docs\/$/g, /\/docs$/g, /^\/$/g];
+
+export const useAuth = () => {
+ const { data: user, ...authStates } = useAuthQuery();
+ const { pathname, replace } = useRouter();
+
+ const [pathAllowed, setPathAllowed] = useState(
+ !regexpUrlsAuth.some((regexp) => !!pathname.match(regexp)),
+ );
+
+ useEffect(() => {
+ setPathAllowed(!regexpUrlsAuth.some((regexp) => !!pathname.match(regexp)));
+ }, [pathname]);
+
+ // Redirect to the path before login
+ useEffect(() => {
+ if (!user) {
+ return;
+ }
+
+ const authUrl = getAuthUrl();
+ if (authUrl) {
+ void replace(authUrl);
+ }
+ }, [user, replace]);
+
+ return { user, authenticated: !!user, pathAllowed, ...authStates };
+};
diff --git a/src/frontend/apps/impress/src/features/auth/index.ts b/src/frontend/apps/impress/src/features/auth/index.ts
new file mode 100644
index 00000000..e2501124
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/index.ts
@@ -0,0 +1,4 @@
+export * from './api/types';
+export * from './components';
+export * from './hooks';
+export * from './utils';
diff --git a/src/frontend/apps/impress/src/features/auth/utils.ts b/src/frontend/apps/impress/src/features/auth/utils.ts
new file mode 100644
index 00000000..766505eb
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/auth/utils.ts
@@ -0,0 +1,27 @@
+import { terminateCrispSession } from '@/services/Crisp';
+
+import { LOGIN_URL, LOGOUT_URL, PATH_AUTH_LOCAL_STORAGE } from './conf';
+
+export const getAuthUrl = () => {
+ const path_auth = localStorage.getItem(PATH_AUTH_LOCAL_STORAGE);
+ if (path_auth) {
+ localStorage.removeItem(PATH_AUTH_LOCAL_STORAGE);
+ return path_auth;
+ }
+};
+
+export const setAuthUrl = () => {
+ if (window.location.pathname !== '/') {
+ localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.pathname);
+ }
+};
+
+export const gotoLogin = () => {
+ setAuthUrl();
+ window.location.replace(LOGIN_URL);
+};
+
+export const gotoLogout = () => {
+ terminateCrispSession();
+ window.location.replace(LOGOUT_URL);
+};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
index 326163dd..976aa680 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
@@ -10,7 +10,7 @@ import { css } from 'styled-components';
import * as Y from 'yjs';
import { Box, TextErrors } from '@/components';
-import { useAuthStore } from '@/core/auth';
+import { useAuth } from '@/features/auth';
import { Doc } from '@/features/docs/doc-management';
import { useUploadFile } from '../hook';
@@ -113,7 +113,7 @@ interface BlockNoteEditorProps {
}
export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
- const { userData } = useAuthStore();
+ const { user } = useAuth();
const { setEditor } = useEditorStore();
const { t } = useTranslation();
@@ -126,7 +126,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const collabName = readOnly
? 'Reader'
- : userData?.full_name || userData?.email || t('Anonymous');
+ : user?.full_name || user?.email || t('Anonymous');
const editor = useCreateBlockNote(
{
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx
index 2b38fe31..6afcc517 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx
@@ -1,4 +1,4 @@
-import { User } from '@/core';
+import { User } from '@/features/auth';
export interface Access {
id: string;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocAccess.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocAccess.tsx
index 9f2edc16..e9f4c0c1 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocAccess.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocAccess.tsx
@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
-import { User } from '@/core/auth';
+import { User } from '@/features/auth';
import {
Access,
Doc,
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocInvitation.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocInvitation.tsx
index 921af05b..47cc2585 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocInvitation.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/api/useCreateDocInvitation.tsx
@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
-import { User } from '@/core/auth';
+import { User } from '@/features/auth';
import { Doc, Role } from '@/features/docs/doc-management';
import { Invitation, OptionType } from '@/features/docs/doc-share/types';
import { ContentLanguage } from '@/i18n/types';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/api/useUsers.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/api/useUsers.tsx
index 54dc8e32..4b7dbda5 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/api/useUsers.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/api/useUsers.tsx
@@ -1,7 +1,7 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
-import { User } from '@/core/auth';
+import { User } from '@/features/auth';
import { Doc } from '@/features/docs/doc-management';
export type UsersParams = {
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx
index 786cc38f..e1282d53 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx
@@ -9,8 +9,8 @@ import { css } from 'styled-components';
import { APIError } from '@/api';
import { Box } from '@/components';
-import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
+import { User } from '@/features/auth';
import { Doc, Role } from '@/features/docs';
import { useLanguage } from '@/i18n/hooks/useLanguage';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberListItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberListItem.tsx
index 24136e11..1164c828 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberListItem.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberListItem.tsx
@@ -2,8 +2,8 @@ import { Button } from '@openfun/cunningham-react';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
-import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
+import { User } from '@/features/auth';
type Props = {
user: User;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitationItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitationItem.tsx
index bc05d17d..6ffc5f33 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitationItem.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitationItem.tsx
@@ -7,8 +7,8 @@ import {
DropdownMenuOption,
IconOptions,
} from '@/components';
-import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
+import { User } from '@/features/auth';
import { Doc, Role } from '@/features/docs/doc-management';
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx
index b9519e41..9f26d059 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx
@@ -10,7 +10,7 @@ import {
QuickSearchData,
QuickSearchGroup,
} from '@/components/quick-search/';
-import { User } from '@/core';
+import { User } from '@/features/auth';
import { Access, Doc } from '@/features/docs';
import { useResponsiveStore } from '@/stores';
import { isValidEmail } from '@/utils';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModalInviteUserByEmail.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModalInviteUserByEmail.tsx
index 17599cdd..0727b92c 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModalInviteUserByEmail.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModalInviteUserByEmail.tsx
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
-import { User } from '@/core';
+import { User } from '@/features/auth';
import { SearchUserRow } from './SearchUserRow';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/SearchUserRow.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/SearchUserRow.tsx
index ce4f9e50..f967a3bc 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/SearchUserRow.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/SearchUserRow.tsx
@@ -3,8 +3,8 @@ import {
QuickSearchItemContent,
QuickSearchItemContentProps,
} from '@/components/quick-search';
-import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
+import { User } from '@/features/auth';
import { UserAvatar } from './UserAvatar';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/UserAvatar.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/UserAvatar.tsx
index 62ee1682..6cf6cac3 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/components/UserAvatar.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/UserAvatar.tsx
@@ -1,8 +1,8 @@
import { css } from 'styled-components';
import { Box } from '@/components';
-import { User } from '@/core';
import { tokens } from '@/cunningham';
+import { User } from '@/features/auth';
const colors = tokens.themes.default.theme.colors;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/hooks/useWhoAmI.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/hooks/useWhoAmI.tsx
index 7ccdbdb3..c5b57b29 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/hooks/useWhoAmI.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/hooks/useWhoAmI.tsx
@@ -1,16 +1,16 @@
-import { useAuthStore } from '@/core/auth';
+import { useAuth } from '@/features/auth';
import { Access, Role } from '@/features/docs/doc-management';
export const useWhoAmI = (access: Access) => {
- const { userData } = useAuthStore();
+ const { user } = useAuth();
- const isMyself = userData?.id === access.user.id;
+ const isMyself = user?.id === access.user.id;
const rolesAllowed = access.abilities.set_role_to;
const isLastOwner =
!rolesAllowed.length && access.role === Role.OWNER && isMyself;
- const isOtherOwner = access.role === Role.OWNER && userData?.id && !isMyself;
+ const isOtherOwner = access.role === Role.OWNER && user?.id && !isMyself;
return {
isLastOwner,
diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/types.tsx
index 58d4848a..e8679693 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-share/types.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-share/types.tsx
@@ -1,4 +1,4 @@
-import { User } from '@/core/auth';
+import { User } from '@/features/auth';
import { Role } from '@/features/docs';
export interface Invitation {
diff --git a/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg b/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg
deleted file mode 100644
index 4f26ae3b..00000000
--- a/src/frontend/apps/impress/src/features/header/assets/icon-docs.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx
new file mode 100644
index 00000000..8504674e
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/header/components/ButtonTogglePanel.tsx
@@ -0,0 +1,26 @@
+import { Button } from '@openfun/cunningham-react';
+import { useTranslation } from 'react-i18next';
+
+import { Icon } from '@/components/';
+import { useLeftPanelStore } from '@/features/left-panel';
+
+export const ButtonTogglePanel = () => {
+ const { t } = useTranslation();
+ const { isPanelOpen, togglePanel } = useLeftPanelStore();
+
+ return (
+