Compare commits

...

4 Commits

Author SHA1 Message Date
Nathan Panchout
225366e92d (e2e) update test suite to use '/docs/' route
- Modify all test files to navigate to '/docs/' instead of '/'
- Add explicit login button click for authentication tests
- Update routing and navigation expectations in test specs
- Introduce new home page test spec
- Ensure consistent authentication flow across test scenarios
2025-01-29 17:21:02 +01:00
Nathan Panchout
2d0a50984b (frontend) implement home page and authentication flow
- Add home page components with responsive design
- Update authentication logic to handle direct login and redirects
- Modify header, layout, and routing to support new home page
- Introduce ProConnectButton for simplified login experience
- Refactor auth store and login mechanisms
2025-01-29 17:19:21 +01:00
Nathan Panchout
7bb6924292 (frontend) add new color and font size tokens for Cunningham
- Add 'primary-action' color token
- Introduce alternative font size tokens (xl-alt, lg-alt, md-alt,
sm-alt, xs-alt)
- Update button border colors to use greyscale-300
2025-01-29 17:19:21 +01:00
Nathan Panchout
3f420fcffc (frontend) add home page assets and update changelog
Add various assets for the home page feature, including logos,
screenshots, and banner images. Update CHANGELOG.md to reflect the new
home page feature.
2025-01-29 17:19:21 +01:00
54 changed files with 770 additions and 162 deletions

View File

@@ -51,6 +51,7 @@ and this project adheres to
- ✨(frontend) add favorite feature #515
- 📝(documentation) Documentation about self-hosted installation #530
- ✨(helm) helm versioning #530
- ✨(frontend) add home page #553
## Changed

BIN
SC2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
await expect(
page.locator('header').first().locator('h2').getByText('Docs'),
).toBeVisible();
@@ -24,6 +24,6 @@ test.describe('404', () => {
page,
}) => {
await page.getByText('Back to home page').click();
await expect(page).toHaveURL('/');
await expect(page).toHaveURL('/docs/');
});
});

View File

@@ -3,7 +3,8 @@ import { test as setup } from '@playwright/test';
import { keyCloakSignIn } from './common';
setup('authenticate-chromium', async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, 'chromium');
await page
.context()
@@ -11,7 +12,8 @@ setup('authenticate-chromium', async ({ page }) => {
});
setup('authenticate-webkit', async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, 'webkit');
await page
.context()
@@ -19,7 +21,8 @@ setup('authenticate-webkit', async ({ page }) => {
});
setup('authenticate-firefox', async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, 'firefox');
await page
.context()

View File

@@ -21,13 +21,43 @@ const config = {
};
test.describe('Config', () => {
test('it checks that media server is configured from config endpoint', async ({
page,
browserName,
}) => {
await page.goto('/docs/');
await createDoc(page, 'doc-media', browserName, 1);
const fileChooserPromise = page.waitForEvent('filechooser');
await page.locator('.bn-block-outer').last().fill('Anything');
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Resizable image with caption').click();
await page.getByText('Upload image').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(__dirname, 'assets/logo-suite-numerique.png'),
);
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
await expect(image).toBeVisible();
// Check src of image
expect(await image.getAttribute('src')).toMatch(
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/,
);
});
test('it checks the config api is called', async ({ page }) => {
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes('/config/') && response.status() === 200,
);
await page.goto('/');
await page.goto('/docs/');
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
@@ -58,62 +88,11 @@ test.describe('Config', () => {
predicate: (msg) => msg.text().includes(invalidMsg),
});
await page.goto('/');
await page.goto('/docs/');
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,
}) => {
await page.goto('/');
await createDoc(page, 'doc-media', browserName, 1);
const fileChooserPromise = page.waitForEvent('filechooser');
await page.locator('.bn-block-outer').last().fill('Anything');
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Resizable image with caption').click();
await page.getByText('Upload image').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(__dirname, 'assets/logo-suite-numerique.png'),
);
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
await expect(image).toBeVisible();
// Check src of image
expect(await image.getAttribute('src')).toMatch(
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/,
);
});
test('it checks that collaboration server is configured from config endpoint', async ({
page,
browserName,
@@ -122,7 +101,7 @@ test.describe('Config', () => {
return webSocket.url().includes('ws://localhost:8083/collaboration/ws/');
});
await page.goto('/');
await page.goto('/docs');
const randomDoc = await createDoc(
page,
@@ -154,10 +133,29 @@ test.describe('Config', () => {
}
});
await page.goto('/');
await page.goto('/docs/');
await expect(
page.locator('#crisp-chatbox').getByText('Invalid website'),
).toBeVisible();
});
});
test.describe('Config footer logo', () => {
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('/docs/');
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
const jsonResponse = await response.json();
expect(jsonResponse.FRONTEND_THEME).toStrictEqual('dsfr');
});
});

View File

@@ -9,7 +9,7 @@ import {
} from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Create', () => {
@@ -62,7 +62,7 @@ test.describe('Doc Create: Not loggued', () => {
);
expect(newDoc.ok()).toBeTruthy();
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
await goToGridDoc(page, { title });

View File

@@ -10,7 +10,7 @@ import {
} from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Editor', () => {
@@ -257,7 +257,7 @@ test.describe('Doc Editor', () => {
await editor.fill('Hello World Doc persisted 2');
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
await page.goto('/');
await page.goto('/docs/');
await goToGridDoc(page, {
title: doc,

View File

@@ -7,7 +7,7 @@ import pdf from 'pdf-parse';
import { createDoc, verifyDocName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Export', () => {

View File

@@ -8,14 +8,14 @@ type SmallDoc = {
};
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Documents Grid mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('it checks the grid when mobile', async ({ page }) => {
@@ -76,7 +76,7 @@ test.describe('Documents Grid mobile', () => {
}
});
await page.goto('/');
await page.goto('/docs/');
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
@@ -195,7 +195,7 @@ test.describe('Document grid item options', () => {
},
});
});
await page.goto('/');
await page.goto('/docs/');
const button = page.getByTestId(
`docs-grid-actions-button-mocked-document-id`,
@@ -286,7 +286,7 @@ test.describe('Documents filters', () => {
test.describe('Documents Grid', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('checks all the elements are visible', async ({ page }) => {

View File

@@ -10,7 +10,7 @@ import {
} from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Header', () => {
@@ -434,7 +434,7 @@ test.describe('Documents Header mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('it checks the copy link button', async ({ page, browserName }) => {

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import { createDoc, randomName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Document create member', () => {

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import { addNewMember, createDoc, goToGridDoc, verifyDocName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Document list members', () => {

View File

@@ -4,7 +4,7 @@ import { keyCloakSignIn, mockedDocument } from './common';
test.describe('Doc Routing', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('Check the presence of the meta tag noindex', async ({ page }) => {
@@ -24,7 +24,7 @@ test.describe('Doc Routing', () => {
});
test('checks alias docs url with homepage', async ({ page }) => {
await expect(page).toHaveURL('/');
await expect(page).toHaveURL('/docs/');
const buttonCreateHomepage = page.getByRole('button', {
name: 'New doc',
@@ -62,17 +62,18 @@ test.describe('Doc Routing: Not loggued', () => {
await mockedDocument(page, { link_reach: 'public' });
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 expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
});
test('The homepage redirects to login.', async ({ page }) => {
await page.goto('/');
await expect(
page.getByRole('button', {
name: 'Sign In',
}),
).toBeVisible();
test('Check is redirected to home page if user is not logged', async ({
page,
}) => {
await page.goto('/docs/');
await expect(page.getByTestId('proconnect-button').first()).toBeVisible();
});
});

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import { createDoc, verifyDocName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Document search', () => {
@@ -15,7 +15,7 @@ test.describe('Document search', () => {
1,
);
await verifyDocName(page, doc1Title);
await page.goto('/');
await page.goto('/docs/');
const [doc2Title] = await createDoc(
page,
@@ -24,7 +24,7 @@ test.describe('Document search', () => {
1,
);
await verifyDocName(page, doc2Title);
await page.goto('/');
await page.goto('/docs/');
await page.getByRole('button', { name: 'search' }).click();
await expect(

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
import { createDoc, verifyDocName } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Table Content', () => {

View File

@@ -8,7 +8,7 @@ import {
} from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Doc Version', () => {

View File

@@ -6,7 +6,7 @@ const browsersName = ['chromium', 'webkit', 'firefox'];
test.describe('Doc Visibility', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('It checks the copy link button', async ({ page, browserName }) => {
@@ -71,7 +71,8 @@ test.describe('Doc Visibility: Restricted', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(
@@ -91,9 +92,9 @@ test.describe('Doc Visibility: Restricted', () => {
})
.click();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
await expect(page.getByTestId('proconnect-button').first()).toBeVisible();
await page.goto(urlDoc);
await page.getByTestId('proconnect-button').first().click();
await expect(page.getByRole('textbox', { name: 'password' })).toBeVisible();
});
@@ -102,7 +103,8 @@ test.describe('Doc Visibility: Restricted', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(page, 'Restricted auth', browserName, 1);
@@ -119,6 +121,7 @@ test.describe('Doc Visibility: Restricted', () => {
const otherBrowser = browsersName.find((b) => b !== browserName);
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, otherBrowser!);
await page.goto(urlDoc);
@@ -130,7 +133,8 @@ test.describe('Doc Visibility: Restricted', () => {
test('A doc is accessible when member.', async ({ page, browserName }) => {
test.slow();
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(page, 'Restricted auth', browserName, 1);
@@ -167,6 +171,7 @@ test.describe('Doc Visibility: Restricted', () => {
})
.click();
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, otherBrowser!);
await page.goto(urlDoc);
@@ -186,7 +191,8 @@ test.describe('Doc Visibility: Public', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(
@@ -247,7 +253,7 @@ test.describe('Doc Visibility: Public', () => {
})
.click();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
await expect(page.getByTestId('proconnect-button').first()).toBeVisible();
await page.goto(urlDoc);
@@ -265,7 +271,8 @@ test.describe('Doc Visibility: Public', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(page, 'Public editable', browserName, 1);
@@ -313,7 +320,7 @@ test.describe('Doc Visibility: Public', () => {
})
.click();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
await expect(page.getByTestId('proconnect-button').first()).toBeVisible();
await page.goto(urlDoc);
@@ -329,7 +336,8 @@ test.describe('Doc Visibility: Authenticated', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(
@@ -364,11 +372,12 @@ test.describe('Doc Visibility: Authenticated', () => {
})
.click();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
await expect(page.getByTestId('proconnect-button').first()).toBeVisible();
await page.goto(urlDoc);
await expect(page.locator('h2').getByText(docTitle)).toBeHidden();
await page.getByTestId('proconnect-button').first().click();
await expect(page.getByRole('textbox', { name: 'password' })).toBeVisible();
});
@@ -376,7 +385,8 @@ test.describe('Doc Visibility: Authenticated', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(
@@ -412,6 +422,7 @@ test.describe('Doc Visibility: Authenticated', () => {
.click();
const otherBrowser = browsersName.find((b) => b !== browserName);
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, otherBrowser!);
await page.goto(urlDoc);
@@ -426,7 +437,8 @@ test.describe('Doc Visibility: Authenticated', () => {
page,
browserName,
}) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
const [docTitle] = await createDoc(
@@ -468,6 +480,7 @@ test.describe('Doc Visibility: Authenticated', () => {
.click();
const otherBrowser = browsersName.find((b) => b !== browserName);
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, otherBrowser!);
await page.goto(urlDoc);

View File

@@ -1,12 +1,13 @@
import { expect, test } from '@playwright/test';
import { goToGridDoc } from './common';
import { keyCloakSignIn } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Footer', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('checks all the elements are visible', async ({ page }) => {
const footer = page.locator('footer').first();
@@ -47,9 +48,14 @@ test.describe('Footer', () => {
).toBeVisible();
});
test('checks footer is not visible on doc editor', async ({ page }) => {
await expect(page.locator('footer')).toBeVisible();
await goToGridDoc(page);
test('checks footer is not visible on doc editor', async ({
page,
browserName,
}) => {
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
await page.goto('/docs/');
await expect(page.locator('footer')).toBeHidden();
});

View File

@@ -4,7 +4,7 @@ import { keyCloakSignIn } from './common';
test.describe('Header', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('checks all the elements are visible', async ({ page }) => {
@@ -69,7 +69,7 @@ test.describe('Header mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('it checks the header when mobile', async ({ page }) => {
@@ -89,7 +89,8 @@ test.describe('Header: Log out', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('checks logout button', async ({ page, browserName }) => {
await page.goto('/');
await page.goto('/docs/');
await page.getByTestId('proconnect-button').first().click();
await keyCloakSignIn(page, browserName);
await page
@@ -98,6 +99,6 @@ test.describe('Header: Log out', () => {
})
.click();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
await expect(page.getByTestId('proconnect-button')).toHaveCount(2);
});
});

View File

@@ -0,0 +1,49 @@
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 app logo' }),
).toBeVisible();
await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
await expect(header.getByText('BETA')).toBeVisible();
// Check the ttile and subtitle are visible
await expect(page.getByText('Collaborative writing made')).toBeVisible();
await expect(page.getByText('Collaborate and write in real')).toBeVisible();
await expect(page.getByText('An uncompromising writing')).toBeVisible();
await expect(page.getByText('Docs offers an intuitive')).toBeVisible();
await expect(page.getByText('Simple and secure')).toBeVisible();
await expect(page.getByText('Docs makes real-time')).toBeVisible();
await expect(page.getByText('Flexible export.')).toBeVisible();
await expect(page.getByText('To facilitate the circulation')).toBeVisible();
await expect(page.getByText('A new way to organize')).toBeVisible();
await expect(page.getByText('Docs transforms your')).toBeVisible();
await expect(page.getByTestId('proconnect-button')).toHaveCount(2);
// Footer - The footer is already tested in its entirety in the footer.spec.ts file
await expect(footer).toBeVisible();
await expect(
page.getByRole('link', { name: 'expand_more See more' }),
).toBeVisible();
});
});

View File

@@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test.describe('Language', () => {

View File

@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
test.describe('Left panel desktop', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('checks all the elements are visible', async ({ page }) => {
@@ -17,7 +17,7 @@ test.describe('Left panel mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.goto('/docs/');
});
test('checks all the desktop elements are hidden and all mobile elements are visible', async ({

View File

@@ -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: {
@@ -379,8 +385,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)',
},

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

View File

@@ -0,0 +1,4 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.2515 62.7264C49.7721 62.0196 52.1861 60.7298 53.7701 58.4505C55.3337 56.2187 55.774 53.3875 55.774 50.6632V10.749C55.774 10.0423 55.7356 9.33431 55.6503 8.63477C56.9023 9.12719 57.8672 9.94087 58.5451 11.0758C59.3448 12.3663 59.7447 14.0657 59.7447 16.1741V56.7153C59.7447 59.5689 59.0449 61.7046 57.6454 63.1223C56.2458 64.54 54.1374 65.2489 51.3202 65.2489H36.0065C36.3692 65.1854 36.7328 65.12 37.0972 65.0528C40.2928 64.4817 43.6701 63.7067 47.2256 62.7336L47.2515 62.7264Z" fill="#C9191E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4521 55.8703V15.2746C10.4521 12.8027 11.1156 10.9306 12.4424 9.65832C13.7874 8.38601 15.5686 7.68624 17.7861 7.55901C21.0577 7.35908 24.1567 7.08644 27.083 6.7411C30.0093 6.37758 32.8084 5.95954 35.4803 5.48697C38.1703 5.01439 40.7876 4.48729 43.3322 3.90567C45.9495 3.28769 47.958 3.59668 49.3575 4.83264C50.757 6.06859 51.4568 8.04067 51.4568 10.7489V50.663C51.4568 53.044 51.0479 54.8161 50.2299 55.9794C49.412 57.1608 48.0307 58.0242 46.0859 58.5695C42.6324 59.5146 39.379 60.2598 36.3254 60.8051C33.2719 61.3685 30.2911 61.7957 27.3829 62.0865C24.4748 62.3773 21.494 62.6045 18.4404 62.7681C15.914 62.9135 13.951 62.3864 12.5515 61.1868C11.1519 60.0053 10.4521 58.2332 10.4521 55.8703ZM20.4387 22.6454C24.4424 22.3905 28.1112 21.9992 31.4447 21.4709C32.2535 21.339 33.0611 21.2017 33.8657 21.0592C34.7076 20.9101 35.3165 20.1764 35.3165 19.3233C35.3165 18.2185 34.3166 17.3865 33.2308 17.5717C32.6071 17.6781 31.9801 17.7815 31.3497 17.8819C28.0496 18.4078 24.4029 18.7979 20.4098 19.052C19.8118 19.0906 19.336 19.2769 19.0302 19.6482C18.741 19.9994 18.5993 20.4218 18.5993 20.9033C18.5993 21.3928 18.7644 21.8188 19.0941 22.1691L19.0987 22.1737C19.456 22.531 19.91 22.6857 20.4387 22.6454ZM20.4374 31.6973C24.4416 31.4424 28.1108 31.051 31.4447 30.5227C34.7931 29.9768 38.1061 29.3397 41.3818 28.6117C42.0465 28.464 42.5444 28.2314 42.7935 27.8701C43.0383 27.5228 43.1594 27.1247 43.1594 26.6834C43.1594 26.1857 42.9698 25.757 42.601 25.4087C42.2038 25.0336 41.6486 24.9462 40.9915 25.0815L40.9873 25.0825C37.8658 25.7902 34.6533 26.4074 31.3497 26.9337C28.0496 27.4596 24.4029 27.8497 20.4098 28.1038C19.8118 28.1424 19.336 28.3287 19.0302 28.7C18.7424 29.0495 18.5993 29.4621 18.5993 29.9278C18.5993 30.4329 18.7625 30.8691 19.0939 31.2211L19.1034 31.2301C19.4595 31.5653 19.9088 31.7177 20.432 31.6976L20.4374 31.6973ZM20.4383 40.7488C24.4422 40.4758 28.111 40.0753 31.4446 39.547C34.7933 39.0192 38.1057 38.3913 41.3818 37.6633C42.0481 37.5152 42.5464 37.2729 42.7951 36.892C43.0376 36.5471 43.1594 36.1594 43.1594 35.735C43.1594 35.2373 42.9698 34.8086 42.601 34.4602C42.2038 34.0852 41.6486 33.9978 40.9915 34.1331L40.9885 34.1338C37.8666 34.8235 34.6537 35.4316 31.3497 35.958C28.0493 36.4839 24.4025 36.8832 20.4092 37.1554C19.8115 37.1941 19.3359 37.3804 19.0302 37.7516C18.7424 38.1011 18.5993 38.5137 18.5993 38.9794C18.5993 39.4845 18.7625 39.9206 19.0939 40.2727L19.1034 40.2816C19.4595 40.6168 19.9088 40.7693 20.432 40.7492L20.4383 40.7488ZM31.4447 48.5358C28.1112 49.0641 24.4424 49.4555 20.4388 49.7103C19.91 49.7506 19.456 49.596 19.0987 49.2386L19.0941 49.2339C18.7645 48.8837 18.5993 48.4577 18.5993 47.9682C18.5993 47.4867 18.741 47.0644 19.0302 46.7132C19.336 46.3418 19.812 46.1555 20.41 46.117C24.4031 45.8629 28.0496 45.4727 31.3497 44.9469C31.9801 44.8464 32.6071 44.743 33.2308 44.6366C34.3166 44.4514 35.3165 45.2834 35.3165 46.3882C35.3165 47.2413 34.7076 47.975 33.8657 48.1241C33.0611 48.2666 32.2535 48.404 31.4447 48.5358Z" fill="#000091"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

View File

@@ -14,12 +14,11 @@ import { useAuthStore } from './useAuthStore';
* 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];
const regexpUrlsAuth = [/\/docs\/$/g];
export const Auth = ({ children }: PropsWithChildren) => {
const { initAuth, initiated, authenticated, login, getAuthUrl } =
useAuthStore();
const { asPath, replace } = useRouter();
const { initAuth, initiated, authenticated, login } = useAuthStore();
const { asPath } = useRouter();
const [pathAllowed, setPathAllowed] = useState<boolean>(
!regexpUrlsAuth.some((regexp) => !!asPath.match(regexp)),
@@ -42,18 +41,6 @@ export const Auth = ({ children }: PropsWithChildren) => {
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 (
<Box $height="100vh" $width="100vw" $align="center" $justify="center">

View File

@@ -9,7 +9,11 @@ export const ButtonLogin = () => {
if (!authenticated) {
return (
<Button onClick={login} color="primary-text" aria-label={t('Login')}>
<Button
onClick={() => login(true)}
color="primary-text"
aria-label={t('Login')}
>
{t('Login')}
</Button>
);

View File

@@ -11,7 +11,7 @@ interface AuthStore {
authenticated: boolean;
initAuth: () => void;
logout: () => void;
login: () => void;
login: (directLogin?: boolean) => void;
setAuthUrl: (url: string) => void;
getAuthUrl: () => string | undefined;
userData?: User;
@@ -37,10 +37,18 @@ export const useAuthStore = create<AuthStore>((set, get) => ({
set({ initiated: true });
});
},
login: () => {
get().setAuthUrl(window.location.pathname);
login: (directLogin?: boolean) => {
if (directLogin) {
get().setAuthUrl(window.location.pathname);
window.location.replace(`${baseApiUrl()}authenticate/`);
return;
}
window.location.replace(`${baseApiUrl()}authenticate/`);
if (window.location.pathname !== '/') {
get().setAuthUrl(window.location.pathname);
window.location.replace(`/`);
return;
}
},
logout: () => {
terminateCrispSession();

View File

@@ -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
@@ -501,10 +507,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
@@ -874,6 +880,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 +1312,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 +1564,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);
}

View File

@@ -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)',
@@ -502,8 +508,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)',
},

View File

@@ -60,14 +60,13 @@ export const Header = () => {
}
/>
)}
<StyledLink href="/">
<StyledLink href="/docs">
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
$margin={{ top: 'auto' }}
>
<Image priority src={IconDocs} alt={t('Docs Logo')} width={25} />
<Title />

View File

@@ -1,26 +1,47 @@
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Text } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
const Title = () => {
type TitleProps = {
size?: 'sm' | 'md';
};
const Title = ({ size = 'sm' }: TitleProps) => {
const { t } = useTranslation();
const theme = useCunninghamTheme();
const spacings = theme.spacingsTokens();
const colors = theme.colorsTokens();
return (
<Box $direction="row" $align="center" $gap={spacings['2xs']}>
<Text $margin="none" as="h2" $color="#000091" $zIndex={1} $size="1.30rem">
<Box
$direction="row"
$align="center"
$gap={size === 'sm' ? spacings['2xs'] : '1.125rem'}
>
<Text
$margin="none"
as="h2"
$color="#000091"
$zIndex={1}
$size={size === 'sm' ? '1.3rem' : spacings['xxl']}
>
{t('Docs')}
</Text>
<Text
$padding={{ horizontal: 'xs', vertical: '1px' }}
$size="11px"
$padding={{
horizontal: size === 'sm' ? '2xs' : 'sm',
vertical: size === 'sm' ? '3xs' : 'xs',
}}
$size={size === 'sm' ? '0.68rem' : spacings['md']}
$theme="primary"
$variation="500"
$weight="bold"
$radius="12px"
$radius={size === 'sm' ? '0.75rem' : '1.5rem'}
$css={css`
line-height: ${size === 'sm' ? '1rem' : '1.25rem'};
`}
$background={colors['primary-200']}
>
BETA

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

View File

@@ -0,0 +1,84 @@
import { Button } from '@openfun/cunningham-react';
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { ProConnectButton } from '@/components/ProConnectButton';
import { useCunninghamTheme } from '@/cunningham';
import { useResponsiveStore } from '@/stores';
import firstImage from '../assets/banner.png';
import DocLogo from '../assets/logo-docs.png';
export default function HomeBanner() {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { isDesktop } = useResponsiveStore();
return (
<Box $maxWidth="78rem" $width="100%" $justify="center" $align="center">
<Box
$width="100%"
$padding={{ top: 'xxxl', bottom: 'calc(3.5rem + 94px)' }}
$justify="space-between"
$align="center"
$height="calc(100dvh - 94px)"
$position="relative"
$direction={isDesktop ? 'row' : 'column'}
>
<Box
$width="100%"
$justify="center"
$align="center"
$padding={{ horizontal: '10px' }}
$gap={spacings['sm']}
>
<Image src={DocLogo} alt="DocLogo" />
<Text
$size={isDesktop ? 'xs-alt' : '2.3rem'}
$variation="800"
$weight="bold"
$textAlign="center"
$css={css`
line-height: 56px;
`}
>
{t('Collaborative writing made simple')}
</Text>
<Text $size="lg" $variation="700" $textAlign="center">
{t(
'Collaborate and write in real time, without layout constraints.',
)}
</Text>
<ProConnectButton />
</Box>
{isDesktop && (
<Image src={firstImage} alt="first" style={{ maxWidth: '50%' }} />
)}
<Box
$position="absolute"
$padding="base"
$justify="center"
$align="center"
$css={css`
bottom: 0;
left: 0;
right: 0;
`}
>
<Button
href="#docs-app-info"
color="secondary"
icon={
<Icon $theme="primary" $variation="800" iconName="expand_more" />
}
>
{t('See more')}
</Button>
</Box>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,125 @@
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import IconDocs from '@/assets/common/logo-docs.svg';
import { Box, Text } from '@/components';
import { ProConnectButton } from '@/components/ProConnectButton';
import { useCunninghamTheme } from '@/cunningham';
import { Footer } from '@/features/footer';
import Title from '@/features/header/components/Title/Title';
import { useLanguage } from '@/i18n/hooks/useLanguage';
import { useResponsiveStore } from '@/stores';
import SC1Responsive from '../assets/SC1-responsive.png';
import SC2 from '../assets/SC2.png';
import SC3 from '../assets/SC3.png';
import SC4Responsive from '../assets/SC4-responsive.png';
import SC4 from '../assets/SC4.png';
import HomeBanner from './HomeBanner';
import { HomeHeader } from './HomeHeader';
import { HomeSection } from './HomeSection';
const GlobalHomePageStyle = createGlobalStyle`
html {
scroll-behavior: smooth;
}
`;
export default function HomeContent() {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const lang = useLanguage();
return (
<>
<GlobalHomePageStyle />
<Box $background="white">
<HomeHeader />
<Box $align="center" $justify="center">
<Box
$maxWidth="78rem"
$width="100%"
$justify="center"
$align="center"
>
<HomeBanner />
<Box
id="docs-app-info"
$gap={isDesktop ? '230px' : '115px'}
$padding={{
vertical: spacings['6xl'],
}}
>
<HomeSection
isColumn={true}
illustration={isDesktop ? undefined : SC1Responsive}
video={
isDesktop ? `assets/SC1-${lang.language}.webm` : undefined
}
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}
illustration={SC2}
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}
reverse={true}
illustration={SC3}
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
illustration={isDesktop ? SC4 : SC4Responsive}
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.',
)}
/>
<Box
$gap={spacings['md']}
$justify="center"
$align="center"
$padding={{ vertical: '140px' }}
>
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
>
<IconDocs />
<Title size="md" />
</Box>
<Text $size="md" $variation="1000" $textAlign="center">
{t('Docs is already available, log in to use it now.')}
</Text>
<ProConnectButton />
</Box>
</Box>
</Box>
</Box>
<Footer />
</Box>
</>
);
}

View File

@@ -0,0 +1,56 @@
import Image from 'next/image';
import IconDocs from '@/assets/common/logo-docs-sm.png';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { LaGaufre } from '@/features/header/components/LaGaufre';
import Title from '@/features/header/components/Title/Title';
import { LanguagePicker } from '@/features/language';
import { useResponsiveStore } from '@/stores';
export const HomeHeader = () => {
const { isDesktop } = useResponsiveStore();
const { themeTokens, spacingsTokens } = useCunninghamTheme();
const logo = themeTokens().logo;
const spacings = spacingsTokens();
return (
<Box
$direction="row"
$justify="space-between"
as="header"
$align="center"
$width="100%"
$padding={{ horizontal: '18px', vertical: 'base' }}
>
<Box $align="center" $gap="3rem" $direction="row">
{logo && (
<Image
priority
src={logo.src}
alt={logo.alt}
width={0}
height={0}
style={{ width: 109, height: 'auto' }}
/>
)}
{isDesktop && (
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
>
<Image src={IconDocs} alt="Docs app logo" />
<Title />
</Box>
)}
</Box>
<Box $direction="row" $gap="1rem" $align="center">
<LanguagePicker />
<LaGaufre />
</Box>
</Box>
);
};

View File

@@ -0,0 +1,143 @@
import Image, { ImageProps } from 'next/image';
import { useMemo } 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 = {
illustration?: ImageProps['src'];
title: string;
description: string;
tag: string;
availableSoon?: boolean;
isColumn?: boolean;
video?: string;
reverse?: boolean;
};
export const HomeSection = ({
illustration,
title,
description,
tag,
reverse = false,
isColumn = true,
availableSoon = false,
video,
}: HomeSectionProps) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { isDesktop } = useResponsiveStore();
const direction = useMemo(() => {
if (!isDesktop) {
return 'column';
} else if (isColumn) {
return reverse ? 'column-reverse' : 'column';
}
return reverse ? 'row-reverse' : 'row';
}, [isColumn, isDesktop, reverse]);
return (
<Box
$direction={direction}
$gap={spacings['lg']}
$padding={{
horizontal: isDesktop ? '6xl' : spacings['md'],
}}
$align={isDesktop ? 'flex-start' : 'center'}
$justify={isDesktop ? 'flex-start' : 'center'}
>
<Box $gap={spacings['sm']} $maxWidth="850px" $width="100%">
<Box $direction="row" $gap={spacings['sm']} $wrap="wrap">
<SectionTag tag={tag} />
{availableSoon && (
<SectionTag tag={t('Available soon')} availableSoon />
)}
</Box>
<Text
$css={css`
line-height: 50px;
`}
$variation="1000"
$weight="bold"
$size={isDesktop ? 'xs-alt' : 'h1'}
>
{title}
</Text>
<Text $variation="700" $weight="400" $size="md">
{description}
</Text>
</Box>
{video && (
<video
preload="none"
loop
muted
autoPlay
src={video}
style={{
width: '100%',
maxWidth: !isDesktop ? 'calc(100dvw - 50px)' : '1200px',
height: 'auto',
}}
>
<source src={video} type="video/webm" />
</video>
)}
{illustration && (
<Image
src={illustration}
alt="SC4Illustration"
style={{
width: '100%',
maxWidth: !isDesktop ? 'calc(100dvw - 50px)' : '1200px',
height: 'auto',
}}
/>
)}
</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>
);
};

View File

@@ -9,7 +9,7 @@ import { LeftPanelFavorites } from './LeftPanelFavorites';
export const LeftPanelContent = () => {
const router = useRouter();
const isHome = router.pathname === '/';
const isHome = router.pathname === '/docs';
const isDoc = router.pathname === '/docs/[id]';
return (

View File

@@ -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,13 @@ import { useResponsiveStore } from '@/stores';
type MainLayoutProps = {
backgroundColor?: 'white' | 'grey';
withoutFooter?: boolean;
withoutLeftPanel?: boolean;
};
export function MainLayout({
children,
backgroundColor = 'white',
withoutFooter = false,
}: PropsWithChildren<MainLayoutProps>) {
const { isDesktop } = useResponsiveStore();
const { colorsTokens } = useCunninghamTheme();
@@ -57,7 +56,6 @@ export function MainLayout({
{children}
</Box>
</Box>
{!withoutFooter && <Footer />}
</div>
);
}

View File

@@ -32,7 +32,7 @@ const Page: NextPageWithLayout = () => {
</Text>
<Box $margin={{ top: 'large' }}>
<StyledLink href="/">
<StyledLink href="/docs/">
<StyledButton>{t('Back to home page')}</StyledButton>
</StyledLink>
</Box>

View File

@@ -33,7 +33,7 @@ export function DocLayout() {
<meta name="robots" content="noindex" />
</Head>
<MainLayout withoutFooter>
<MainLayout>
<DocPage id={id} />
</MainLayout>
</>
@@ -46,6 +46,7 @@ interface DocProps {
const DocPage = ({ id }: DocProps) => {
const { login } = useAuthStore();
const {
data: docQuery,
isError,

View File

@@ -1,6 +1,7 @@
import { useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import type { ReactElement } from 'react';
import { useAuthStore } from '@/core';
import { DocDefaultFilter } from '@/features/docs';
import { DocsGrid } from '@/features/docs/docs-grid';
import { MainLayout } from '@/layouts';
@@ -9,6 +10,14 @@ import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const searchParams = useSearchParams();
const target = searchParams.get('target');
const router = useRouter();
const auth = useAuthStore();
const url = auth.getAuthUrl();
if (auth.authenticated && url) {
router.replace(url);
return null;
}
return <DocsGrid target={target as DocDefaultFilter} />;
};

View File

@@ -1,3 +1,16 @@
import Docs from './docs';
import { useRouter } from 'next/navigation';
export default Docs;
import { useAuthStore } from '@/core';
import HomeContent from '@/features/home/components/HomeContent';
export default function Index() {
const router = useRouter();
const auth = useAuthStore();
if (auth.authenticated) {
router.push('/docs/');
return null;
}
return <HomeContent />;
}