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 ( + + {t('ProConnect + + ); +}; 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 ( + + )} + + {!isMobile && ( + {t('Banner + )} + + + + + + ); +} diff --git a/src/frontend/apps/impress/src/features/home/components/HomeBottom.tsx b/src/frontend/apps/impress/src/features/home/components/HomeBottom.tsx new file mode 100644 index 00000000..7f2dbad9 --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/HomeBottom.tsx @@ -0,0 +1,159 @@ +import Image from 'next/image'; +import { Trans, useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import DocLogo from '@/assets/icons/icon-docs.svg?url'; +import { Box, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { ProConnectButton } from '@/features/auth'; +import { Title } from '@/features/header'; +import { useResponsiveStore } from '@/stores'; + +import SC5 from '../assets/SC5.png'; + +import { HomeSection } from './HomeSection'; + +export function HomeBottom() { + const { componentTokens } = useCunninghamTheme(); + const withProConnect = componentTokens()['home-proconnect'].activated; + + if (withProConnect) { + return ; + } else { + return ; + } +} + +function HomeOpenSource() { + const { t } = useTranslation(); + const { colorsTokens } = useCunninghamTheme(); + const { isTablet } = useResponsiveStore(); + + return ( + + + + Docs is built on top of{' '} + + Django Rest Framework + + ,{' '} + + Next.js + + , and{' '} + + MinIO + + . We also use{' '} + + Yjs + {' '} + and{' '} + + BlockNote.js + {' '} + of which we are proud sponsors. + + + + + You can easily self-host Docs (check our installation{' '} + + documentation + {' '} + with production-ready examples). +
+ Docs uses an innovation and business friendly{' '} + + licence + + .
+ Contributions are welcome (see our roadmap{' '} + + here + + ). +
+
+ + + Docs is the result of a joint effort lead by the French 🇫🇷🥖 + + (DINUM) + {' '} + and German 🇩🇪🥨 governments{' '} + + (ZenDiS) + + . We are always looking for new public partners (we are currently + onboarding the Netherlands 🇳🇱🧀). Feel free to reach out if you + are interested in using or contributing to docs. + + + + } + /> + ); +} + +function HomeProConnect() { + const { t } = useTranslation(); + const { spacingsTokens } = useCunninghamTheme(); + const spacings = spacingsTokens(); + const { isMobile } = useResponsiveStore(); + const parentGap = '230px'; + + return ( + + + + DocLogo + + </Box> + <Text $size="md" $variation="1000" $textAlign="center"> + {t('Docs is already available, log in to use it now.')} + </Text> + <ProConnectButton /> + </Box> + </Box> + ); +} diff --git a/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx new file mode 100644 index 00000000..6e9dc7d9 --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/HomeContent.tsx @@ -0,0 +1,120 @@ +import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import { Box } from '@/components'; +import { Footer } from '@/features/footer'; +import { LeftPanel } from '@/features/left-panel'; +import { useLanguage } from '@/i18n/hooks/useLanguage'; +import { useResponsiveStore } from '@/stores'; + +import SC1ResponsiveEn from '../assets/SC1-responsive-en.png'; +import SC1ResponsiveFr from '../assets/SC1-responsive-fr.png'; +import SC2En from '../assets/SC2-en.png'; +import SC2Fr from '../assets/SC2-fr.png'; +import SC3En from '../assets/SC3-en.png'; +import SC3Fr from '../assets/SC3-fr.png'; +import SC4En from '../assets/SC4-en.png'; +import SC4Fr from '../assets/SC4-fr.png'; +import SC4ResponsiveEn from '../assets/SC4-responsive-en.png'; +import SC4ResponsiveFr from '../assets/SC4-responsive-fr.png'; + +import HomeBanner from './HomeBanner'; +import { HomeBottom } from './HomeBottom'; +import { HomeHeader, getHeaderHeight } from './HomeHeader'; +import { HomeSection } from './HomeSection'; + +export function HomeContent() { + const { t } = useTranslation(); + const { isMobile, isSmallMobile } = useResponsiveStore(); + const lang = useLanguage(); + const isFrLanguage = lang.language === 'fr'; + + return ( + <Box as="main"> + <HomeHeader /> + {isSmallMobile && ( + <Box $css="& .panel-header{display: none;}"> + <LeftPanel /> + </Box> + )} + <Box + $css={css` + height: calc(100vh - ${getHeaderHeight(isSmallMobile)}px); + overflow-y: auto; + `} + > + <Box + $align="center" + $justify="center" + $maxWidth="1120px" + $padding={{ horizontal: isSmallMobile ? '1rem' : '3rem' }} + $width="100%" + $margin="auto" + > + <HomeBanner /> + <Box + id="docs-app-info" + $maxWidth="100%" + $gap={isMobile ? '115px' : '230px'} + $padding={{ bottom: '3rem' }} + > + <HomeSection + isColumn={true} + isSmallDevice={isMobile} + illustration={isFrLanguage ? SC1ResponsiveFr : SC1ResponsiveEn} + video={ + isFrLanguage ? `/assets/SC1-fr.webm` : `/assets/SC1-en.webm` + } + title={t('An uncompromising writing experience.')} + tag={t('Write')} + description={t( + 'Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.', + )} + /> + <HomeSection + isColumn={false} + isSmallDevice={isMobile} + illustration={isFrLanguage ? SC2Fr : SC2En} + title={t('Simple and secure collaboration.')} + tag={t('Collaborate')} + description={t( + 'Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.', + )} + /> + <HomeSection + isColumn={false} + isSmallDevice={isMobile} + reverse={true} + illustration={isFrLanguage ? SC3Fr : SC3En} + title={t('Flexible export.')} + tag={t('Export')} + description={t( + 'To facilitate the circulation of documents, Docs allows you to export your content to the most common formats: PDF, Word or OpenDocument.', + )} + /> + <HomeSection + isSmallDevice={isMobile} + illustration={ + isMobile + ? isFrLanguage + ? SC4ResponsiveFr + : SC4ResponsiveEn + : isFrLanguage + ? SC4Fr + : SC4En + } + title={t('A new way to organize knowledge.')} + tag={t('Organize')} + availableSoon={true} + description={t( + 'Docs transforms your documents into knowledge bases thanks to subpages, powerful search and the ability to pin your important documents.', + )} + /> + <HomeBottom /> + </Box> + </Box> + <Footer /> + </Box> + </Box> + ); +} diff --git a/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx b/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx new file mode 100644 index 00000000..8e7a331d --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx @@ -0,0 +1,76 @@ +import Image from 'next/image'; +import { useTranslation } from 'react-i18next'; + +import { default as IconDocs } from '@/assets/icons/icon-docs.svg?url'; +import { Box } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { ButtonTogglePanel, Title } from '@/features/header/'; +import { LaGaufre } from '@/features/header/components/LaGaufre'; +import { LanguagePicker } from '@/features/language'; +import { useResponsiveStore } from '@/stores'; + +export const HEADER_HEIGHT = 91; +export const HEADER_HEIGHT_MOBILE = 52; + +export const getHeaderHeight = (isSmallMobile: boolean) => + isSmallMobile ? HEADER_HEIGHT_MOBILE : HEADER_HEIGHT; + +export const HomeHeader = () => { + const { t } = useTranslation(); + const { themeTokens, spacingsTokens } = useCunninghamTheme(); + const spacings = spacingsTokens(); + const logo = themeTokens().logo; + const { isSmallMobile } = useResponsiveStore(); + + return ( + <Box + $direction="row" + $justify="space-between" + as="header" + $align="center" + $width="100%" + $padding={{ horizontal: 'small' }} + $height={`${isSmallMobile ? HEADER_HEIGHT_MOBILE : HEADER_HEIGHT}px`} + > + <Box + $align="center" + $gap="2rem" + $direction="row" + $width={isSmallMobile ? '100%' : 'auto'} + $justify="center" + > + {isSmallMobile && ( + <Box $position="absolute" $css="left: 1rem;"> + <ButtonTogglePanel /> + </Box> + )} + {!isSmallMobile && logo && ( + <Image + priority + src={logo.src} + alt={logo.alt} + width={0} + height={0} + style={{ width: logo.widthHeader, height: 'auto' }} + /> + )} + <Box + $align="center" + $gap={spacings['3xs']} + $direction="row" + $position="relative" + $height="fit-content" + > + <Image priority src={IconDocs} alt={t('Docs Logo')} width={32} /> + <Title /> + </Box> + </Box> + {!isSmallMobile && ( + <Box $direction="row" $gap="1rem" $align="center"> + <LanguagePicker /> + <LaGaufre /> + </Box> + )} + </Box> + ); +}; diff --git a/src/frontend/apps/impress/src/features/home/components/HomeSection.tsx b/src/frontend/apps/impress/src/features/home/components/HomeSection.tsx new file mode 100644 index 00000000..956acd4c --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/HomeSection.tsx @@ -0,0 +1,218 @@ +import Image, { ImageProps } from 'next/image'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import { Box, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { useResponsiveStore } from '@/stores'; + +export type HomeSectionProps = { + description: React.ReactNode; + tag: string; + title: string; + availableSoon?: boolean; + illustration?: ImageProps['src']; + isColumn?: boolean; + isSmallDevice?: boolean; + reverse?: boolean; + textWidth?: string; + video?: string; +}; + +export const HomeSection = ({ + availableSoon = false, + description, + illustration, + isSmallDevice, + isColumn = true, + reverse = false, + tag, + textWidth = '50%', + title, + video, +}: HomeSectionProps) => { + const { t } = useTranslation(); + const { spacingsTokens } = useCunninghamTheme(); + const spacings = spacingsTokens(); + + const { isSmallMobile } = useResponsiveStore(); + + const direction = useMemo(() => { + if (isSmallDevice) { + return 'column'; + } else if (isColumn) { + return reverse ? 'column-reverse' : 'column'; + } + + return reverse ? 'row-reverse' : 'row'; + }, [isColumn, isSmallDevice, reverse]); + + const [isVisible, setIsVisible] = useState(false); + const sectionRef = useRef<HTMLDivElement>(null); + + /** + * Intersection Observer to trigger animation when the section is in the viewport + */ + useEffect(() => { + const currentSection = sectionRef.current; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsVisible(true); + observer.disconnect(); + } + }); + }, + { threshold: 0.1 }, + ); + + if (currentSection) { + observer.observe(currentSection); + } + + return () => { + if (currentSection) { + observer.unobserve(currentSection); + } + }; + }, []); + + return ( + <Box + ref={sectionRef} + $align="center" + $justify="center" + $width="100%" + $hasTransition="slow" + $css={css` + opacity: ${isVisible ? 1 : 0}; + `} + > + <Box + $direction={direction} + $gap={!isSmallDevice ? spacings['lg'] : spacings['sm']} + $maxHeight="100%" + $height="auto" + $width="100%" + > + <Box + $gap={spacings['sm']} + $maxWidth="850px" + $css={direction === 'column' ? '100%' : `flex-basis: ${textWidth};`} + > + <Box $direction="row" $gap={spacings['sm']} $wrap="wrap"> + <SectionTag tag={tag} /> + {availableSoon && ( + <SectionTag tag={t('Available soon')} availableSoon /> + )} + </Box> + <Text + as="h2" + $css={css` + line-height: ${!isSmallDevice ? '50px' : 'normal'}; + `} + $variation="1000" + $weight="bold" + $size={!isSmallDevice ? 'xs-alt' : 'h4'} + $textAlign={isSmallMobile ? 'center' : 'left'} + $margin="none" + > + {title} + </Text> + <Text $variation="700" $weight="400" $size="md"> + {description} + </Text> + </Box> + + {video && !isSmallDevice && ( + <Box<'video'> + as="video" + preload="none" + loop + muted + autoPlay + src={video} + $css={css` + width: 100%; + max-width: ${isSmallDevice ? 'calc(100dvw - 50px)' : '1200px'}; + height: auto; + overflow: auto; + margin: auto; + `} + onPlay={(e) => { + const videoElement = e.currentTarget; + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) { + videoElement.pause(); + } else { + setTimeout(() => { + void videoElement.play(); + }, 500); + } + }); + }, + { threshold: 0.1 }, + ); + observer.observe(videoElement); + }} + > + <source src={video} type="video/webm" /> + </Box> + )} + + {illustration && (isSmallDevice || !video) && ( + <Image + src={illustration} + alt={t('Illustration')} + style={{ + width: 'fit-content', + maxWidth: '100%', + height: 'fit-content', + margin: 'auto', + overflow: 'auto', + flexBasis: direction === 'column' ? '100%' : '50%', + }} + /> + )} + </Box> + </Box> + ); +}; + +const SectionTag = ({ + tag, + availableSoon, +}: { + tag: string; + availableSoon?: boolean; +}) => { + const { colorsTokens, spacingsTokens } = useCunninghamTheme(); + const spacings = spacingsTokens(); + const colors = colorsTokens(); + return ( + <Box + $background={ + !availableSoon ? colors['primary-100'] : colors['warning-100'] + } + $padding={{ horizontal: spacings['sm'], vertical: '6px' }} + $css={css` + align-self: flex-start; + border-radius: 4px; + `} + > + <Text + $size="md" + $variation={availableSoon ? '600' : '800'} + $weight="bold" + $theme={availableSoon ? 'warning' : 'primary'} + > + {tag} + </Text> + </Box> + ); +}; diff --git a/src/frontend/apps/impress/src/features/home/components/index.ts b/src/frontend/apps/impress/src/features/home/components/index.ts new file mode 100644 index 00000000..73b53e00 --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/components/index.ts @@ -0,0 +1 @@ +export * from './HomeContent'; diff --git a/src/frontend/apps/impress/src/features/home/index.ts b/src/frontend/apps/impress/src/features/home/index.ts new file mode 100644 index 00000000..07635cbb --- /dev/null +++ b/src/frontend/apps/impress/src/features/home/index.ts @@ -0,0 +1 @@ +export * from './components'; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanel.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanel.tsx index 3733b0f9..d0eb7292 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanel.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanel.tsx @@ -3,8 +3,8 @@ import { useCallback, useEffect } from 'react'; import { createGlobalStyle, css } from 'styled-components'; import { Box, SeparatedSection } from '@/components'; -import { ButtonLogin } from '@/core'; import { useCunninghamTheme } from '@/cunningham'; +import { ButtonLogin } from '@/features/auth'; import { HEADER_HEIGHT } from '@/features/header/conf'; import { LanguagePicker } from '@/features/language'; import { useResponsiveStore } from '@/stores'; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx index d001d6a2..2a494e11 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx @@ -4,7 +4,7 @@ import { useRouter } from 'next/navigation'; import { PropsWithChildren } from 'react'; import { Box, Icon, SeparatedSection } from '@/components'; -import { useAuthStore } from '@/core'; +import { useAuth } from '@/features/auth'; import { useCreateDoc } from '@/features/docs/doc-management'; import { DocSearchModal } from '@/features/docs/doc-search'; import { useCmdK } from '@/hook/useCmdK'; @@ -14,7 +14,7 @@ import { useLeftPanelStore } from '../stores'; export const LeftPanelHeader = ({ children }: PropsWithChildren) => { const router = useRouter(); const searchModal = useModal(); - const auth = useAuthStore(); + const { authenticated } = useAuth(); useCmdK(searchModal.open); const { togglePanel } = useLeftPanelStore(); @@ -36,7 +36,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => { return ( <> - <Box $width="100%"> + <Box $width="100%" className="panel-header"> <SeparatedSection> <Box $padding={{ horizontal: 'sm' }} @@ -57,7 +57,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => { </span> } /> - {auth.authenticated && ( + {authenticated && ( <Button onClick={searchModal.open} size="medium" @@ -75,7 +75,7 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => { /> )} </Box> - {auth.authenticated && ( + {authenticated && ( <Button onClick={createNewDoc}>{t('New doc')}</Button> )} </Box> diff --git a/src/frontend/apps/impress/src/layouts/MainLayout.tsx b/src/frontend/apps/impress/src/layouts/MainLayout.tsx index 81523571..114e256a 100644 --- a/src/frontend/apps/impress/src/layouts/MainLayout.tsx +++ b/src/frontend/apps/impress/src/layouts/MainLayout.tsx @@ -3,7 +3,6 @@ import { css } from 'styled-components'; import { Box } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Footer } from '@/features/footer'; import { Header } from '@/features/header'; import { HEADER_HEIGHT } from '@/features/header/conf'; import { LeftPanel } from '@/features/left-panel'; @@ -12,13 +11,11 @@ import { useResponsiveStore } from '@/stores'; type MainLayoutProps = { backgroundColor?: 'white' | 'grey'; - withoutFooter?: boolean; }; export function MainLayout({ children, backgroundColor = 'white', - withoutFooter = false, }: PropsWithChildren<MainLayoutProps>) { const { isDesktop } = useResponsiveStore(); const { colorsTokens } = useCunninghamTheme(); @@ -57,7 +54,6 @@ export function MainLayout({ {children} </Box> </Box> - {!withoutFooter && <Footer />} </div> ); } diff --git a/src/frontend/apps/impress/src/layouts/PageLayout.tsx b/src/frontend/apps/impress/src/layouts/PageLayout.tsx index 8efaeccc..f52706af 100644 --- a/src/frontend/apps/impress/src/layouts/PageLayout.tsx +++ b/src/frontend/apps/impress/src/layouts/PageLayout.tsx @@ -2,13 +2,18 @@ import { PropsWithChildren } from 'react'; import { Box } from '@/components'; import { Footer } from '@/features/footer'; -import { Header } from '@/features/header'; +import { HEADER_HEIGHT, Header } from '@/features/header'; +import { LeftPanel } from '@/features/left-panel'; +import { useResponsiveStore } from '@/stores'; export function PageLayout({ children }: PropsWithChildren) { + const { isDesktop } = useResponsiveStore(); + return ( - <Box $minHeight="100vh"> + <Box $minHeight="100vh" $margin={{ top: `${HEADER_HEIGHT}px` }}> <Header /> <Box as="main" $width="100%" $css="flex-grow:1;"> + {!isDesktop && <LeftPanel />} {children} </Box> <Footer /> diff --git a/src/frontend/apps/impress/src/pages/_app.tsx b/src/frontend/apps/impress/src/pages/_app.tsx index 6af42f45..bd46e8c3 100644 --- a/src/frontend/apps/impress/src/pages/_app.tsx +++ b/src/frontend/apps/impress/src/pages/_app.tsx @@ -1,5 +1,6 @@ import type { AppProps } from 'next/app'; import Head from 'next/head'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { AppProvider } from '@/core/'; @@ -17,6 +18,14 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page); const { t } = useTranslation(); + useEffect(() => { + console.log( + `%c + \r\n \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \r\n \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \r\n \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \r\n \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \r\n \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \r\n \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \r\n \r`, + 'font-size: 11px;line-height:15px;background-image: linear-gradient(#000091, #005f91);color: transparent;background-clip: text;', + ); + }, []); + return ( <> <Head> diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx index 95b29a5d..fdd26602 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import { Box, Text, TextErrors } from '@/components'; -import { useAuthStore } from '@/core/auth'; +import { gotoLogin } from '@/features/auth'; import { DocEditor } from '@/features/docs/doc-editor'; import { Doc, @@ -33,7 +33,7 @@ export function DocLayout() { <meta name="robots" content="noindex" /> </Head> - <MainLayout withoutFooter> + <MainLayout> <DocPage id={id} /> </MainLayout> </> @@ -45,7 +45,6 @@ interface DocProps { } const DocPage = ({ id }: DocProps) => { - const { login } = useAuthStore(); const { data: docQuery, isError, @@ -106,7 +105,7 @@ const DocPage = ({ id }: DocProps) => { } if (error.status === 401) { - login(); + gotoLogin(); return null; } diff --git a/src/frontend/apps/impress/src/pages/login/index.tsx b/src/frontend/apps/impress/src/pages/login/index.tsx new file mode 100644 index 00000000..dca21233 --- /dev/null +++ b/src/frontend/apps/impress/src/pages/login/index.tsx @@ -0,0 +1,8 @@ +import { HomeContent } from '@/features/home'; +import { NextPageWithLayout } from '@/types/next'; + +const Page: NextPageWithLayout = () => { + return <HomeContent />; +}; + +export default Page; diff --git a/src/frontend/apps/impress/src/services/Crisp.tsx b/src/frontend/apps/impress/src/services/Crisp.tsx index ad165a0f..673cabc8 100644 --- a/src/frontend/apps/impress/src/services/Crisp.tsx +++ b/src/frontend/apps/impress/src/services/Crisp.tsx @@ -4,7 +4,7 @@ import { Crisp } from 'crisp-sdk-web'; -import { User } from '@/core'; +import { User } from '@/features/auth'; export const initializeCrispSession = (user: User) => { if (!Crisp.isCrispInjected()) { diff --git a/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx b/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx index 4b202161..7da3133c 100644 --- a/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx +++ b/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx @@ -23,13 +23,13 @@ const initialState = { }; export const useResponsiveStore = create<UseResponsiveStore>((set) => ({ + isDesktop: initialState.isDesktop, isMobile: initialState.isMobile, - isTablet: initialState.isTablet, isSmallMobile: initialState.isSmallMobile, + isTablet: initialState.isTablet, screenSize: initialState.screenSize, screenWidth: initialState.screenWidth, setScreenSize: (size: ScreenSize) => set(() => ({ screenSize: size })), - isDesktop: initialState.isDesktop, initializeResizeListener: () => { const resizeHandler = () => { const width = window.innerWidth; @@ -38,14 +38,14 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({ isDesktop: false, screenSize: 'small-mobile', isMobile: true, - isTablet: false, + isTablet: true, isSmallMobile: true, }); } else if (width < 768) { set({ isDesktop: false, screenSize: 'mobile', - isTablet: false, + isTablet: true, isMobile: true, isSmallMobile: false, }); diff --git a/src/mail/mjml/partial/header.mjml b/src/mail/mjml/partial/header.mjml index 0eb2fc6a..57dbd28b 100644 --- a/src/mail/mjml/partial/header.mjml +++ b/src/mail/mjml/partial/header.mjml @@ -13,7 +13,7 @@ <mj-all font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" font-size="16px" - line-height="1.5em" + line-height="1.3em" color="#3A3A3A" /> </mj-attributes> @@ -22,6 +22,7 @@ h1, h2, h3, h4, h5, h6, p { margin: 0; padding: 0; + line-height: normal; } a { @@ -33,7 +34,6 @@ h1 { color: #161616; font-size: 1.4rem; - font-weight: 700; } .wrapper {