✏️(frontend) change all occurences of pad to doc
The initial doc was named pad in reference to the "NotePad de l'Etat", but we renamed it doc to clearly dissociate the 2 sites. Doc is also a small reference of google doc.
@@ -32,6 +32,8 @@ and this project adheres to
|
|||||||
- ⚡️(e2e) unique login between tests (#80)
|
- ⚡️(e2e) unique login between tests (#80)
|
||||||
- ⚡️(CI) improve e2e job (#86)
|
- ⚡️(CI) improve e2e job (#86)
|
||||||
- ♻️(frontend) improve the error and message info ui (#93)
|
- ♻️(frontend) improve the error and message info ui (#93)
|
||||||
|
- ♻️(frontend) improve the error and message info ui (#93)
|
||||||
|
- ✏️(frontend) change all occurences of pad to doc (#99)
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ export const randomName = (name: string, browserName: string, length: number) =>
|
|||||||
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
|
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createPad = async (
|
export const createDoc = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
padName: string,
|
docName: string,
|
||||||
browserName: string,
|
browserName: string,
|
||||||
length: number,
|
length: number,
|
||||||
isPublic: boolean = false,
|
isPublic: boolean = false,
|
||||||
@@ -38,11 +38,11 @@ export const createPad = async (
|
|||||||
name: 'Create the document',
|
name: 'Create the document',
|
||||||
});
|
});
|
||||||
|
|
||||||
const randomPads = randomName(padName, browserName, length);
|
const randomDocs = randomName(docName, browserName, length);
|
||||||
|
|
||||||
for (let i = 0; i < randomPads.length; i++) {
|
for (let i = 0; i < randomDocs.length; i++) {
|
||||||
await panel.getByRole('button', { name: 'Add a document' }).click();
|
await panel.getByRole('button', { name: 'Add a document' }).click();
|
||||||
await page.getByText('Document name').fill(randomPads[i]);
|
await page.getByText('Document name').fill(randomDocs[i]);
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
await page.getByText('Is it public ?').click();
|
await page.getByText('Is it public ?').click();
|
||||||
@@ -50,10 +50,10 @@ export const createPad = async (
|
|||||||
|
|
||||||
await expect(buttonCreate).toBeEnabled();
|
await expect(buttonCreate).toBeEnabled();
|
||||||
await buttonCreate.click();
|
await buttonCreate.click();
|
||||||
await expect(panel.locator('li').getByText(randomPads[i])).toBeVisible();
|
await expect(panel.locator('li').getByText(randomDocs[i])).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomPads;
|
return randomDocs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTemplate = async (
|
export const createTemplate = async (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { createPad, randomName } from './common';
|
import { createDoc, randomName } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -12,7 +12,7 @@ test.describe('Document add users', () => {
|
|||||||
(response) =>
|
(response) =>
|
||||||
response.url().includes('/users/?q=user') && response.status() === 200,
|
response.url().includes('/users/?q=user') && response.status() === 200,
|
||||||
);
|
);
|
||||||
await createPad(page, 'select-multi-users', browserName, 1);
|
await createDoc(page, 'select-multi-users', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
await page.getByRole('button', { name: 'Add members' }).click();
|
||||||
@@ -73,7 +73,7 @@ test.describe('Document add users', () => {
|
|||||||
response.url().includes('/users/?q=user') && response.status() === 200,
|
response.url().includes('/users/?q=user') && response.status() === 200,
|
||||||
);
|
);
|
||||||
|
|
||||||
await createPad(page, 'user-invitation', browserName, 1);
|
await createDoc(page, 'user-invitation', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
await page.getByRole('button', { name: 'Add members' }).click();
|
||||||
@@ -125,7 +125,7 @@ test.describe('Document add users', () => {
|
|||||||
response.url().includes('/users/?q=user') && response.status() === 200,
|
response.url().includes('/users/?q=user') && response.status() === 200,
|
||||||
);
|
);
|
||||||
|
|
||||||
await createPad(page, 'user-twice', browserName, 1);
|
await createDoc(page, 'user-twice', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
await page.getByRole('button', { name: 'Add members' }).click();
|
||||||
@@ -165,7 +165,7 @@ test.describe('Document add users', () => {
|
|||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
await createPad(page, 'invitation-twice', browserName, 1);
|
await createDoc(page, 'invitation-twice', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Add members' }).click();
|
await page.getByRole('button', { name: 'Add members' }).click();
|
||||||
@@ -4,8 +4,8 @@ test.beforeEach(async ({ page }) => {
|
|||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Pad Create', () => {
|
test.describe('Doc Create', () => {
|
||||||
test('checks all the create pad elements are visible', async ({ page }) => {
|
test('checks all the create doc elements are visible', async ({ page }) => {
|
||||||
const buttonCreateHomepage = page.getByRole('button', {
|
const buttonCreateHomepage = page.getByRole('button', {
|
||||||
name: 'Create a new document',
|
name: 'Create a new document',
|
||||||
});
|
});
|
||||||
@@ -58,7 +58,7 @@ test.describe('Pad Create', () => {
|
|||||||
await expect(buttonCreateHomepage).toBeVisible();
|
await expect(buttonCreateHomepage).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks the routing on new pad created', async ({
|
test('checks the routing on new doc created', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -66,21 +66,21 @@ test.describe('Pad Create', () => {
|
|||||||
|
|
||||||
await panel.getByRole('button', { name: 'Add a document' }).click();
|
await panel.getByRole('button', { name: 'Add a document' }).click();
|
||||||
|
|
||||||
const padName = `My routing pad ${browserName}-${Math.floor(Math.random() * 1000)}`;
|
const docName = `My routing doc ${browserName}-${Math.floor(Math.random() * 1000)}`;
|
||||||
await page.getByText('Document name').fill(padName);
|
await page.getByText('Document name').fill(docName);
|
||||||
await page.getByRole('button', { name: 'Create the document' }).click();
|
await page.getByRole('button', { name: 'Create the document' }).click();
|
||||||
|
|
||||||
const elPad = page.locator('h2').getByText(padName);
|
const elDoc = page.locator('h2').getByText(docName);
|
||||||
await expect(elPad).toBeVisible();
|
await expect(elDoc).toBeVisible();
|
||||||
|
|
||||||
await panel.getByRole('button', { name: 'Add a document' }).click();
|
await panel.getByRole('button', { name: 'Add a document' }).click();
|
||||||
await expect(elPad).toBeHidden();
|
await expect(elDoc).toBeHidden();
|
||||||
|
|
||||||
await panel.locator('li').getByText(padName).click();
|
await panel.locator('li').getByText(docName).click();
|
||||||
await expect(elPad).toBeVisible();
|
await expect(elDoc).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks alias pads url with homepage', async ({ page }) => {
|
test('checks alias docs url with homepage', async ({ page }) => {
|
||||||
await expect(page).toHaveURL('/');
|
await expect(page).toHaveURL('/');
|
||||||
|
|
||||||
const buttonCreateHomepage = page.getByRole('button', {
|
const buttonCreateHomepage = page.getByRole('button', {
|
||||||
@@ -98,7 +98,7 @@ test.describe('Pad Create', () => {
|
|||||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
await page.goto('/docs/some-unknown-pad');
|
await page.goto('/docs/some-unknown-doc');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(
|
page.getByText(
|
||||||
'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
|
'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
|
||||||
@@ -108,8 +108,8 @@ test.describe('Pad Create', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks that the pad is public', async ({ page, browserName }) => {
|
test('checks that the doc is public', async ({ page, browserName }) => {
|
||||||
const responsePromisePad = page.waitForResponse(
|
const responsePromiseDoc = page.waitForResponse(
|
||||||
(response) =>
|
(response) =>
|
||||||
response.url().includes('/documents/') && response.status() === 201,
|
response.url().includes('/documents/') && response.status() === 201,
|
||||||
);
|
);
|
||||||
@@ -118,13 +118,13 @@ test.describe('Pad Create', () => {
|
|||||||
|
|
||||||
await panel.getByRole('button', { name: 'Add a document' }).click();
|
await panel.getByRole('button', { name: 'Add a document' }).click();
|
||||||
|
|
||||||
const padName = `My routing pad ${browserName}-${Math.floor(Math.random() * 1000)}`;
|
const docName = `My routing doc ${browserName}-${Math.floor(Math.random() * 1000)}`;
|
||||||
await page.getByText('Document name').fill(padName);
|
await page.getByText('Document name').fill(docName);
|
||||||
await page.getByText('Is it public ?').click();
|
await page.getByText('Is it public ?').click();
|
||||||
await page.getByRole('button', { name: 'Create the document' }).click();
|
await page.getByRole('button', { name: 'Create the document' }).click();
|
||||||
|
|
||||||
const responsePad = await responsePromisePad;
|
const responseDoc = await responsePromiseDoc;
|
||||||
const is_public = (await responsePad.json()).is_public;
|
const is_public = (await responseDoc.json()).is_public;
|
||||||
expect(is_public).toBeTruthy();
|
expect(is_public).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { createPad } from './common';
|
import { createDoc } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Pad Editor', () => {
|
test.describe('Doc Editor', () => {
|
||||||
test('checks the Pad Editor interact correctly', async ({
|
test('checks the Doc Editor interact correctly', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const randomPad = await createPad(page, 'pad-editor', browserName, 1);
|
const randomDoc = await createDoc(page, 'doc-editor', browserName, 1);
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
|
||||||
|
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
||||||
await expect(page.getByText('Hello World')).toBeVisible();
|
await expect(page.getByText('Hello World')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks the Pad is connected to the webrtc server', async ({
|
test('checks the Doc is connected to the webrtc server', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -28,8 +28,8 @@ test.describe('Pad Editor', () => {
|
|||||||
return webSocket.url().includes('ws://localhost:4444/');
|
return webSocket.url().includes('ws://localhost:4444/');
|
||||||
});
|
});
|
||||||
|
|
||||||
const randomPad = await createPad(page, 'pad-editor', browserName, 1);
|
const randomDoc = await createDoc(page, 'doc-editor', browserName, 1);
|
||||||
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
|
||||||
|
|
||||||
const webSocket = await webSocketPromise;
|
const webSocket = await webSocketPromise;
|
||||||
expect(webSocket.url()).toContain('ws://localhost:4444/');
|
expect(webSocket.url()).toContain('ws://localhost:4444/');
|
||||||
@@ -52,9 +52,9 @@ test.describe('Pad Editor', () => {
|
|||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const randomPad = await createPad(page, 'pad-markdown', browserName, 1);
|
const randomDoc = await createDoc(page, 'doc-markdown', browserName, 1);
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText(randomPad[0])).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible();
|
||||||
|
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page
|
await page
|
||||||
@@ -74,57 +74,57 @@ test.describe('Pad Editor', () => {
|
|||||||
).toHaveAttribute('href', 'http://test-markdown.html');
|
).toHaveAttribute('href', 'http://test-markdown.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders correctly when we switch from one pad to another', async ({
|
test('it renders correctly when we switch from one doc to another', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const [firstPad, secondPad] = await createPad(
|
const [firstDoc, secondDoc] = await createDoc(
|
||||||
page,
|
page,
|
||||||
'pad-multiple',
|
'doc-multiple',
|
||||||
browserName,
|
browserName,
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
|
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
|
|
||||||
// Check the first pad
|
// Check the first doc
|
||||||
await panel.getByText(firstPad).click();
|
await panel.getByText(firstDoc).click();
|
||||||
await expect(page.locator('h2').getByText(firstPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 1');
|
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1');
|
||||||
await expect(page.getByText('Hello World Pad 1')).toBeVisible();
|
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
|
||||||
|
|
||||||
// Check the second pad
|
// Check the second doc
|
||||||
await panel.getByText(secondPad).click();
|
await panel.getByText(secondDoc).click();
|
||||||
await expect(page.locator('h2').getByText(secondPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(secondDoc)).toBeVisible();
|
||||||
await expect(page.getByText('Hello World Pad 1')).toBeHidden();
|
await expect(page.getByText('Hello World Doc 1')).toBeHidden();
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World Pad 2');
|
await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2');
|
||||||
await expect(page.getByText('Hello World Pad 2')).toBeVisible();
|
await expect(page.getByText('Hello World Doc 2')).toBeVisible();
|
||||||
|
|
||||||
// Check the first pad again
|
// Check the first doc again
|
||||||
await panel.getByText(firstPad).click();
|
await panel.getByText(firstDoc).click();
|
||||||
await expect(page.locator('h2').getByText(firstPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(firstDoc)).toBeVisible();
|
||||||
await expect(page.getByText('Hello World Pad 2')).toBeHidden();
|
await expect(page.getByText('Hello World Doc 2')).toBeHidden();
|
||||||
await expect(page.getByText('Hello World Pad 1')).toBeVisible();
|
await expect(page.getByText('Hello World Doc 1')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it saves the doc when we change pages', async ({
|
test('it saves the doc when we change pages', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const [pad] = await createPad(page, 'pad-save-page', browserName, 1);
|
const [doc] = await createDoc(page, 'doc-save-page', browserName, 1);
|
||||||
|
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
|
|
||||||
// Check the first pad
|
// Check the first doc
|
||||||
await panel.getByText(pad).click();
|
await panel.getByText(doc).click();
|
||||||
await expect(page.locator('h2').getByText(pad)).toBeVisible();
|
await expect(page.locator('h2').getByText(doc)).toBeVisible();
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page
|
await page
|
||||||
.locator('.ProseMirror.bn-editor')
|
.locator('.ProseMirror.bn-editor')
|
||||||
.fill('Hello World Pad persisted 1');
|
.fill('Hello World Doc persisted 1');
|
||||||
await expect(page.getByText('Hello World Pad persisted 1')).toBeVisible();
|
await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
|
||||||
|
|
||||||
await panel
|
await panel
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
@@ -142,33 +142,33 @@ test.describe('Pad Editor', () => {
|
|||||||
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
|
||||||
await panel.getByText(pad).click();
|
await panel.getByText(doc).click();
|
||||||
|
|
||||||
await expect(page.getByText('Hello World Pad persisted 1')).toBeVisible();
|
await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it saves the doc when we quit pages', async ({ page, browserName }) => {
|
test('it saves the doc when we quit pages', async ({ page, browserName }) => {
|
||||||
// eslint-disable-next-line playwright/no-skipped-test
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
test.skip(browserName === 'webkit', 'This test is very flaky with webkit');
|
test.skip(browserName === 'webkit', 'This test is very flaky with webkit');
|
||||||
|
|
||||||
const [pad] = await createPad(page, 'pad-save-quit', browserName, 1);
|
const [doc] = await createDoc(page, 'doc-save-quit', browserName, 1);
|
||||||
|
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
|
|
||||||
// Check the first pad
|
// Check the first doc
|
||||||
await panel.getByText(pad).click();
|
await panel.getByText(doc).click();
|
||||||
await expect(page.locator('h2').getByText(pad)).toBeVisible();
|
await expect(page.locator('h2').getByText(doc)).toBeVisible();
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page
|
await page
|
||||||
.locator('.ProseMirror.bn-editor')
|
.locator('.ProseMirror.bn-editor')
|
||||||
.fill('Hello World Pad persisted 2');
|
.fill('Hello World Doc persisted 2');
|
||||||
await expect(page.getByText('Hello World Pad persisted 2')).toBeVisible();
|
await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
|
||||||
|
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
|
||||||
await panel.getByText(pad).click();
|
await panel.getByText(doc).click();
|
||||||
|
|
||||||
await expect(page.getByText('Hello World Pad persisted 2')).toBeVisible();
|
await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it cannot edit if viewer', async ({ page, browserName }) => {
|
test('it cannot edit if viewer', async ({ page, browserName }) => {
|
||||||
@@ -202,7 +202,7 @@ test.describe('Pad Editor', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPad(page, 'pad-right-edit', browserName, 1);
|
await createDoc(page, 'doc-right-edit', browserName, 1);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(
|
page.getByText(
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { addNewMember, createPad } from './common';
|
import { addNewMember, createDoc } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -11,7 +11,7 @@ test.describe('Members Delete', () => {
|
|||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
await createPad(page, 'member-delete-1', browserName, 1);
|
await createDoc(page, 'member-delete-1', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Manage members' }).click();
|
await page.getByRole('button', { name: 'Manage members' }).click();
|
||||||
@@ -37,7 +37,7 @@ test.describe('Members Delete', () => {
|
|||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
await createPad(page, 'member-delete-2', browserName, 1);
|
await createDoc(page, 'member-delete-2', browserName, 1);
|
||||||
|
|
||||||
await addNewMember(page, 0, 'Owner');
|
await addNewMember(page, 0, 'Owner');
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ test.describe('Members Delete', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it cannot delete owner member', async ({ page, browserName }) => {
|
test('it cannot delete owner member', async ({ page, browserName }) => {
|
||||||
await createPad(page, 'member-delete-3', browserName, 1);
|
await createDoc(page, 'member-delete-3', browserName, 1);
|
||||||
|
|
||||||
const username = await addNewMember(page, 0, 'Owner');
|
const username = await addNewMember(page, 0, 'Owner');
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ test.describe('Members Delete', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it deletes admin member', async ({ page, browserName }) => {
|
test('it deletes admin member', async ({ page, browserName }) => {
|
||||||
await createPad(page, 'member-delete-4', browserName, 1);
|
await createDoc(page, 'member-delete-4', browserName, 1);
|
||||||
|
|
||||||
const username = await addNewMember(page, 0, 'Admin');
|
const username = await addNewMember(page, 0, 'Admin');
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ test.describe('Members Delete', () => {
|
|||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
await createPad(page, 'member-delete-5', browserName, 1);
|
await createDoc(page, 'member-delete-5', browserName, 1);
|
||||||
|
|
||||||
const username = await addNewMember(page, 0, 'Owner');
|
const username = await addNewMember(page, 0, 'Owner');
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ test.describe('Members Delete', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it deletes admin member when admin', async ({ page, browserName }) => {
|
test('it deletes admin member when admin', async ({ page, browserName }) => {
|
||||||
await createPad(page, 'member-delete-6', browserName, 1);
|
await createDoc(page, 'member-delete-6', browserName, 1);
|
||||||
|
|
||||||
// To not be the only owner
|
// To not be the only owner
|
||||||
await addNewMember(page, 0, 'Owner');
|
await addNewMember(page, 0, 'Owner');
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { createPad } from './common';
|
import { createDoc } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -8,7 +8,7 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
test.describe('Document grid members', () => {
|
test.describe('Document grid members', () => {
|
||||||
test('it display the grid', async ({ page, browserName }) => {
|
test('it display the grid', async ({ page, browserName }) => {
|
||||||
await createPad(page, 'grid-display', browserName, 1);
|
await createDoc(page, 'grid-display', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Manage members' }).click();
|
await page.getByRole('button', { name: 'Manage members' }).click();
|
||||||
@@ -91,7 +91,7 @@ test.describe('Document grid members', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await createPad(page, 'grid-no-member', browserName, 1);
|
await createDoc(page, 'grid-no-member', browserName, 1);
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page.getByRole('button', { name: 'Manage members' }).click();
|
await page.getByRole('button', { name: 'Manage members' }).click();
|
||||||
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
|
|||||||
|
|
||||||
import { waitForElementCount } from '../helpers';
|
import { waitForElementCount } from '../helpers';
|
||||||
|
|
||||||
import { createPad } from './common';
|
import { createDoc } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -146,16 +146,16 @@ test.describe('Documents Panel', () => {
|
|||||||
|
|
||||||
test('checks the hover and selected state', async ({ page, browserName }) => {
|
test('checks the hover and selected state', async ({ page, browserName }) => {
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
await createPad(page, 'pad-hover', browserName, 2);
|
await createDoc(page, 'doc-hover', browserName, 2);
|
||||||
|
|
||||||
const selectedPad = panel.locator('li').nth(0);
|
const selectedDoc = panel.locator('li').nth(0);
|
||||||
await expect(selectedPad).toHaveCSS(
|
await expect(selectedDoc).toHaveCSS(
|
||||||
'background-color',
|
'background-color',
|
||||||
'rgb(202, 202, 251)',
|
'rgb(202, 202, 251)',
|
||||||
);
|
);
|
||||||
|
|
||||||
const hoverPad = panel.locator('li').nth(1);
|
const hoverDoc = panel.locator('li').nth(1);
|
||||||
await hoverPad.hover();
|
await hoverDoc.hover();
|
||||||
await expect(hoverPad).toHaveCSS('background-color', 'rgb(227, 227, 253)');
|
await expect(hoverDoc).toHaveCSS('background-color', 'rgb(227, 227, 253)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,24 +2,24 @@ import { expect, test } from '@playwright/test';
|
|||||||
import cs from 'convert-stream';
|
import cs from 'convert-stream';
|
||||||
import pdf from 'pdf-parse';
|
import pdf from 'pdf-parse';
|
||||||
|
|
||||||
import { createPad } from './common';
|
import { createDoc } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Pad Tools', () => {
|
test.describe('Doc Tools', () => {
|
||||||
test('it converts the pad to pdf with a template integrated', async ({
|
test('it converts the doc to pdf with a template integrated', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const [randomPad] = await createPad(page, 'pad-editor', browserName, 1);
|
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||||
|
|
||||||
const downloadPromise = page.waitForEvent('download', (download) => {
|
const downloadPromise = page.waitForEvent('download', (download) => {
|
||||||
return download.suggestedFilename().includes(`${randomPad}.pdf`);
|
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
|
||||||
|
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await page.locator('.ProseMirror.bn-editor').click();
|
||||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
||||||
@@ -38,19 +38,19 @@ test.describe('Pad Tools', () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
const download = await downloadPromise;
|
const download = await downloadPromise;
|
||||||
expect(download.suggestedFilename()).toBe(`${randomPad}.pdf`);
|
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
|
||||||
|
|
||||||
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
|
||||||
const pdfText = (await pdf(pdfBuffer)).text;
|
const pdfText = (await pdf(pdfBuffer)).text;
|
||||||
|
|
||||||
expect(pdfText).toContain('Hello World'); // This is the pad text
|
expect(pdfText).toContain('Hello World'); // This is the doc text
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it converts the blocknote json in correct html for the pdf', async ({
|
test('it converts the blocknote json in correct html for the pdf', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
}) => {
|
}) => {
|
||||||
const [randomPad] = await createPad(page, 'pad-editor', browserName, 1);
|
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||||
let body = '';
|
let body = '';
|
||||||
|
|
||||||
await page.route('**/templates/*/generate-document/', async (route) => {
|
await page.route('**/templates/*/generate-document/', async (route) => {
|
||||||
@@ -60,7 +60,7 @@ test.describe('Pad Tools', () => {
|
|||||||
await route.continue();
|
await route.continue();
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||||
await page.locator('.bn-block-outer').last().click();
|
await page.locator('.bn-block-outer').last().click();
|
||||||
@@ -85,15 +85,15 @@ test.describe('Pad Tools', () => {
|
|||||||
expect(body).toContain('<br/><br/>');
|
expect(body).toContain('<br/><br/>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it updates the pad', async ({ page, browserName }) => {
|
test('it updates the doc', async ({ page, browserName }) => {
|
||||||
const [randomPad] = await createPad(
|
const [randomDoc] = await createDoc(
|
||||||
page,
|
page,
|
||||||
'pad-update',
|
'doc-update',
|
||||||
browserName,
|
browserName,
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page
|
await page
|
||||||
@@ -103,14 +103,14 @@ test.describe('Pad Tools', () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('h2').getByText(`Update document "${randomPad}"`),
|
page.locator('h2').getByText(`Update document "${randomDoc}"`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('checkbox', { name: 'Is it public ?' }),
|
page.getByRole('checkbox', { name: 'Is it public ?' }),
|
||||||
).toBeChecked();
|
).toBeChecked();
|
||||||
|
|
||||||
await page.getByText('Document name').fill(`${randomPad}-updated`);
|
await page.getByText('Document name').fill(`${randomDoc}-updated`);
|
||||||
await page.getByText('Is it public ?').click();
|
await page.getByText('Is it public ?').click();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@@ -125,7 +125,7 @@ test.describe('Pad Tools', () => {
|
|||||||
|
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
await expect(
|
await expect(
|
||||||
panel.locator('li').getByText(`${randomPad}-updated`),
|
panel.locator('li').getByText(`${randomDoc}-updated`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
@@ -140,9 +140,9 @@ test.describe('Pad Tools', () => {
|
|||||||
).not.toBeChecked();
|
).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it deletes the pad', async ({ page, browserName }) => {
|
test('it deletes the doc', async ({ page, browserName }) => {
|
||||||
const [randomPad] = await createPad(page, 'pad-delete', browserName, 1);
|
const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1);
|
||||||
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
|
await expect(page.locator('h2').getByText(randomDoc)).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('Open the document options').click();
|
await page.getByLabel('Open the document options').click();
|
||||||
await page
|
await page
|
||||||
@@ -152,7 +152,7 @@ test.describe('Pad Tools', () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('h2').getByText(`Deleting the document "${randomPad}"`),
|
page.locator('h2').getByText(`Deleting the document "${randomDoc}"`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@@ -170,7 +170,7 @@ test.describe('Pad Tools', () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
const panel = page.getByLabel('Documents panel').first();
|
const panel = page.getByLabel('Documents panel').first();
|
||||||
await expect(panel.locator('li').getByText(randomPad)).toBeHidden();
|
await expect(panel.locator('li').getByText(randomDoc)).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it checks the options available if administrator', async ({
|
test('it checks the options available if administrator', async ({
|
||||||
@@ -207,7 +207,7 @@ test.describe('Pad Tools', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPad(page, 'pad-tools-right-admin', browserName, 1);
|
await createDoc(page, 'doc-tools-right-admin', browserName, 1);
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ test.describe('Pad Tools', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPad(page, 'pad-tools-right-editor', browserName, 1);
|
await createDoc(page, 'doc-tools-right-editor', browserName, 1);
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ test.describe('Pad Tools', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createPad(page, 'pad-tools-right-reader', browserName, 1);
|
await createDoc(page, 'doc-tools-right-reader', browserName, 1);
|
||||||
|
|
||||||
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
await expect(page.locator('h2').getByText('Mocked document')).toBeVisible();
|
||||||
|
|
||||||
@@ -6,10 +6,10 @@ export const baseApiUrl = (apiVersion: string = '1.0') => {
|
|||||||
return `${origin}/api/v${apiVersion}/`;
|
return `${origin}/api/v${apiVersion}/`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signalingUrl = (padId: string) => {
|
export const signalingUrl = (docId: string) => {
|
||||||
const base =
|
const base =
|
||||||
process.env.NEXT_PUBLIC_SIGNALING_URL ||
|
process.env.NEXT_PUBLIC_SIGNALING_URL ||
|
||||||
(typeof window !== 'undefined' ? `wss://${window.location.host}/ws` : '');
|
(typeof window !== 'undefined' ? `wss://${window.location.host}/ws` : '');
|
||||||
|
|
||||||
return `${base}/${padId}`;
|
return `${base}/${docId}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,41 +7,41 @@ import { WebrtcProvider } from 'y-webrtc';
|
|||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { useAuthStore } from '@/core/auth';
|
import { useAuthStore } from '@/core/auth';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import useSavePad from '../hook/useSavePad';
|
import useSaveDoc from '../hook/useSaveDoc';
|
||||||
import { usePadStore } from '../stores';
|
import { useDocStore } from '../stores';
|
||||||
import { randomColor } from '../utils';
|
import { randomColor } from '../utils';
|
||||||
|
|
||||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||||
|
|
||||||
interface BlockNoteEditorProps {
|
interface BlockNoteEditorProps {
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlockNoteEditor = ({ pad }: BlockNoteEditorProps) => {
|
export const BlockNoteEditor = ({ doc }: BlockNoteEditorProps) => {
|
||||||
const { createProvider, padsStore } = usePadStore();
|
const { createProvider, docsStore } = useDocStore();
|
||||||
const provider = padsStore?.[pad.id]?.provider;
|
const provider = docsStore?.[doc.id]?.provider;
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
createProvider(pad.id, pad.content);
|
createProvider(doc.id, doc.content);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BlockNoteContent pad={pad} provider={provider} />;
|
return <BlockNoteContent doc={doc} provider={provider} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BlockNoteContentProps {
|
interface BlockNoteContentProps {
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
provider: WebrtcProvider;
|
provider: WebrtcProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
|
export const BlockNoteContent = ({ doc, provider }: BlockNoteContentProps) => {
|
||||||
const { userData } = useAuthStore();
|
const { userData } = useAuthStore();
|
||||||
const { setEditor, padsStore } = usePadStore();
|
const { setEditor, docsStore } = useDocStore();
|
||||||
useSavePad(pad.id, provider.doc, pad.abilities.partial_update);
|
useSaveDoc(doc.id, provider.doc, doc.abilities.partial_update);
|
||||||
|
|
||||||
const storedEditor = padsStore?.[pad.id]?.editor;
|
const storedEditor = docsStore?.[doc.id]?.editor;
|
||||||
const editor = useMemo(() => {
|
const editor = useMemo(() => {
|
||||||
if (storedEditor) {
|
if (storedEditor) {
|
||||||
return storedEditor;
|
return storedEditor;
|
||||||
@@ -60,8 +60,8 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
|
|||||||
}, [provider, storedEditor, userData?.email]);
|
}, [provider, storedEditor, userData?.email]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditor(pad.id, editor);
|
setEditor(doc.id, editor);
|
||||||
}, [setEditor, pad.id, editor]);
|
}, [setEditor, doc.id, editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -77,7 +77,7 @@ export const BlockNoteContent = ({ pad, provider }: BlockNoteContentProps) => {
|
|||||||
<BlockNoteView
|
<BlockNoteView
|
||||||
editor={editor}
|
editor={editor}
|
||||||
formattingToolbar={false}
|
formattingToolbar={false}
|
||||||
editable={pad.abilities.partial_update}
|
editable={doc.abilities.partial_update}
|
||||||
>
|
>
|
||||||
<BlockNoteToolbar />
|
<BlockNoteToolbar />
|
||||||
</BlockNoteView>
|
</BlockNoteView>
|
||||||
@@ -2,16 +2,16 @@ import { Alert, VariantType } from '@openfun/cunningham-react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Box, Card, Text } from '@/components';
|
import { Box, Card, Text } from '@/components';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { PadToolBox } from '@/features/pads/pad-tools';
|
import { DocToolBox } from '@/features/docs/doc-tools';
|
||||||
|
|
||||||
import { BlockNoteEditor } from './BlockNoteEditor';
|
import { BlockNoteEditor } from './BlockNoteEditor';
|
||||||
|
|
||||||
interface PadEditorProps {
|
interface DocEditorProps {
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PadEditor = ({ pad }: PadEditorProps) => {
|
export const DocEditor = ({ doc }: DocEditorProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@@ -21,11 +21,11 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
|
|||||||
$position="relative"
|
$position="relative"
|
||||||
>
|
>
|
||||||
<Text as="h2" $align="center" $margin="auto">
|
<Text as="h2" $align="center" $margin="auto">
|
||||||
{pad.title}
|
{doc.title}
|
||||||
</Text>
|
</Text>
|
||||||
<PadToolBox pad={pad} />
|
<DocToolBox doc={doc} />
|
||||||
</Box>
|
</Box>
|
||||||
{!pad.abilities.partial_update && (
|
{!doc.abilities.partial_update && (
|
||||||
<Box className="m-b" $css="margin-top:0;">
|
<Box className="m-b" $css="margin-top:0;">
|
||||||
<Alert
|
<Alert
|
||||||
type={VariantType.WARNING}
|
type={VariantType.WARNING}
|
||||||
@@ -38,7 +38,7 @@ export const PadEditor = ({ pad }: PadEditorProps) => {
|
|||||||
$css="flex:1;"
|
$css="flex:1;"
|
||||||
$overflow="auto"
|
$overflow="auto"
|
||||||
>
|
>
|
||||||
<BlockNoteEditor pad={pad} />
|
<BlockNoteEditor doc={doc} />
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './DocEditor';
|
||||||
@@ -2,12 +2,12 @@ import { useRouter } from 'next/router';
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { useUpdatePad } from '@/features/pads/pad-management/';
|
import { useUpdateDoc } from '@/features/docs/doc-management/';
|
||||||
|
|
||||||
import { toBase64 } from '../utils';
|
import { toBase64 } from '../utils';
|
||||||
|
|
||||||
const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
|
const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
|
||||||
const { mutate: updatePad } = useUpdatePad();
|
const { mutate: updateDoc } = useUpdateDoc();
|
||||||
const [initialDoc, setInitialDoc] = useState<string>(
|
const [initialDoc, setInitialDoc] = useState<string>(
|
||||||
toBase64(Y.encodeStateAsUpdate(doc)),
|
toBase64(Y.encodeStateAsUpdate(doc)),
|
||||||
);
|
);
|
||||||
@@ -40,7 +40,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
|
|||||||
};
|
};
|
||||||
}, [doc]);
|
}, [doc]);
|
||||||
|
|
||||||
const savePad = useCallback(() => {
|
const saveDoc = useCallback(() => {
|
||||||
const newDoc = toBase64(Y.encodeStateAsUpdate(doc));
|
const newDoc = toBase64(Y.encodeStateAsUpdate(doc));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,11 +52,11 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
|
|||||||
|
|
||||||
setInitialDoc(newDoc);
|
setInitialDoc(newDoc);
|
||||||
|
|
||||||
updatePad({
|
updateDoc({
|
||||||
id: padId,
|
id: docId,
|
||||||
content: newDoc,
|
content: newDoc,
|
||||||
});
|
});
|
||||||
}, [initialDoc, padId, doc, updatePad, canSave]);
|
}, [initialDoc, docId, doc, updateDoc, canSave]);
|
||||||
|
|
||||||
const timeout = useRef<NodeJS.Timeout>();
|
const timeout = useRef<NodeJS.Timeout>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -67,7 +67,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
savePad();
|
saveDoc();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save every minute
|
// Save every minute
|
||||||
@@ -82,7 +82,7 @@ const useSavePad = (padId: string, doc: Y.Doc, canSave: boolean) => {
|
|||||||
removeEventListener('beforeunload', onSave);
|
removeEventListener('beforeunload', onSave);
|
||||||
router.events.off('routeChangeStart', onSave);
|
router.events.off('routeChangeStart', onSave);
|
||||||
};
|
};
|
||||||
}, [router.events, savePad]);
|
}, [router.events, saveDoc]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useSavePad;
|
export default useSaveDoc;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './useDocStore';
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { BlockNoteEditor } from '@blocknote/core';
|
||||||
|
import { WebrtcProvider } from 'y-webrtc';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
import { signalingUrl } from '@/core';
|
||||||
|
import { Base64, Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
|
export interface DocStore {
|
||||||
|
docsStore: {
|
||||||
|
[docId: Doc['id']]: {
|
||||||
|
provider: WebrtcProvider;
|
||||||
|
editor?: BlockNoteEditor;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
createProvider: (docId: Doc['id'], initialDoc: Base64) => WebrtcProvider;
|
||||||
|
setEditor: (docId: Doc['id'], editor: BlockNoteEditor) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
docsStore: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDocStore = create<DocStore>((set) => ({
|
||||||
|
docsStore: initialState.docsStore,
|
||||||
|
createProvider: (docId: string, initialDoc: Base64) => {
|
||||||
|
const doc = new Y.Doc({
|
||||||
|
guid: docId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (initialDoc) {
|
||||||
|
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = new WebrtcProvider(docId, doc, {
|
||||||
|
signaling: [signalingUrl(docId)],
|
||||||
|
maxConns: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
set(({ docsStore }) => {
|
||||||
|
return {
|
||||||
|
docsStore: {
|
||||||
|
...docsStore,
|
||||||
|
[docId]: {
|
||||||
|
provider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
},
|
||||||
|
setEditor: (docId, editor) => {
|
||||||
|
set(({ docsStore }) => {
|
||||||
|
return {
|
||||||
|
docsStore: {
|
||||||
|
...docsStore,
|
||||||
|
[docId]: {
|
||||||
|
...docsStore[docId],
|
||||||
|
editor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './useDoc';
|
||||||
|
export * from './useDocs';
|
||||||
|
export * from './useUpdateDoc';
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
import { Doc, KEY_LIST_DOC } from '@/features/docs';
|
||||||
|
|
||||||
|
type CreateDocParam = Pick<Doc, 'title' | 'is_public'>;
|
||||||
|
|
||||||
|
export const createDoc = async ({
|
||||||
|
title,
|
||||||
|
is_public,
|
||||||
|
}: CreateDocParam): Promise<Doc> => {
|
||||||
|
const response = await fetchAPI(`documents/`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
is_public,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError('Failed to create the doc', await errorCauses(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<Doc>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CreateDocProps {
|
||||||
|
onSuccess: (data: Doc) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateDoc({ onSuccess }: CreateDocProps) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<Doc, APIError, CreateDocParam>({
|
||||||
|
mutationFn: createDoc,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [KEY_LIST_DOC],
|
||||||
|
});
|
||||||
|
onSuccess(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
|
||||||
|
import { Doc } from '../types';
|
||||||
|
|
||||||
|
export type DocParams = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
|
||||||
|
const response = await fetchAPI(`documents/${id}/`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new APIError('Failed to get the doc', await errorCauses(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json() as Promise<Doc>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KEY_DOC = 'doc';
|
||||||
|
|
||||||
|
export function useDoc(
|
||||||
|
param: DocParams,
|
||||||
|
queryConfig?: UseQueryOptions<Doc, APIError, Doc>,
|
||||||
|
) {
|
||||||
|
return useQuery<Doc, APIError, Doc>({
|
||||||
|
queryKey: [KEY_DOC, param],
|
||||||
|
queryFn: () => getDoc(param),
|
||||||
|
...queryConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,59 +6,59 @@ import {
|
|||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export enum PadsOrdering {
|
export enum DocsOrdering {
|
||||||
BY_CREATED_ON = 'created_at',
|
BY_CREATED_ON = 'created_at',
|
||||||
BY_CREATED_ON_DESC = '-created_at',
|
BY_CREATED_ON_DESC = '-created_at',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PadsParams = {
|
export type DocsParams = {
|
||||||
ordering: PadsOrdering;
|
ordering: DocsOrdering;
|
||||||
};
|
};
|
||||||
type PadsAPIParams = PadsParams & {
|
type DocsAPIParams = DocsParams & {
|
||||||
page: number;
|
page: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PadsResponse = APIList<Pad>;
|
type DocsResponse = APIList<Doc>;
|
||||||
|
|
||||||
export const getPads = async ({
|
export const getDocs = async ({
|
||||||
ordering,
|
ordering,
|
||||||
page,
|
page,
|
||||||
}: PadsAPIParams): Promise<PadsResponse> => {
|
}: DocsAPIParams): Promise<DocsResponse> => {
|
||||||
const orderingQuery = ordering ? `&ordering=${ordering}` : '';
|
const orderingQuery = ordering ? `&ordering=${ordering}` : '';
|
||||||
const response = await fetchAPI(`documents/?page=${page}${orderingQuery}`);
|
const response = await fetchAPI(`documents/?page=${page}${orderingQuery}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new APIError('Failed to get the pads', await errorCauses(response));
|
throw new APIError('Failed to get the docs', await errorCauses(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json() as Promise<PadsResponse>;
|
return response.json() as Promise<DocsResponse>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const KEY_LIST_PAD = 'pads';
|
export const KEY_LIST_DOC = 'docs';
|
||||||
|
|
||||||
export function usePads(
|
export function useDocs(
|
||||||
param: PadsParams,
|
param: DocsParams,
|
||||||
queryConfig?: DefinedInitialDataInfiniteOptions<
|
queryConfig?: DefinedInitialDataInfiniteOptions<
|
||||||
PadsResponse,
|
DocsResponse,
|
||||||
APIError,
|
APIError,
|
||||||
InfiniteData<PadsResponse>,
|
InfiniteData<DocsResponse>,
|
||||||
QueryKey,
|
QueryKey,
|
||||||
number
|
number
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return useInfiniteQuery<
|
return useInfiniteQuery<
|
||||||
PadsResponse,
|
DocsResponse,
|
||||||
APIError,
|
APIError,
|
||||||
InfiniteData<PadsResponse>,
|
InfiniteData<DocsResponse>,
|
||||||
QueryKey,
|
QueryKey,
|
||||||
number
|
number
|
||||||
>({
|
>({
|
||||||
initialPageParam: 1,
|
initialPageParam: 1,
|
||||||
queryKey: [KEY_LIST_PAD, param],
|
queryKey: [KEY_LIST_DOC, param],
|
||||||
queryFn: ({ pageParam }) =>
|
queryFn: ({ pageParam }) =>
|
||||||
getPads({
|
getDocs({
|
||||||
...param,
|
...param,
|
||||||
page: pageParam,
|
page: pageParam,
|
||||||
}),
|
}),
|
||||||
@@ -6,32 +6,32 @@ import {
|
|||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
|
|
||||||
import { KEY_LIST_PAD } from './usePads';
|
import { KEY_LIST_DOC } from './useDocs';
|
||||||
|
|
||||||
interface RemovePadProps {
|
interface RemoveDocProps {
|
||||||
padId: string;
|
docId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removePad = async ({ padId }: RemovePadProps): Promise<void> => {
|
export const removeDoc = async ({ docId }: RemoveDocProps): Promise<void> => {
|
||||||
const response = await fetchAPI(`documents/${padId}/`, {
|
const response = await fetchAPI(`documents/${docId}/`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new APIError('Failed to delete the pad', await errorCauses(response));
|
throw new APIError('Failed to delete the doc', await errorCauses(response));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseRemovePadOptions = UseMutationOptions<void, APIError, RemovePadProps>;
|
type UseRemoveDocOptions = UseMutationOptions<void, APIError, RemoveDocProps>;
|
||||||
|
|
||||||
export const useRemovePad = (options?: UseRemovePadOptions) => {
|
export const useRemoveDoc = (options?: UseRemoveDocOptions) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<void, APIError, RemovePadProps>({
|
return useMutation<void, APIError, RemoveDocProps>({
|
||||||
mutationFn: removePad,
|
mutationFn: removeDoc,
|
||||||
...options,
|
...options,
|
||||||
onSuccess: (data, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_LIST_PAD],
|
queryKey: [KEY_LIST_DOC],
|
||||||
});
|
});
|
||||||
if (options?.onSuccess) {
|
if (options?.onSuccess) {
|
||||||
options.onSuccess(data, variables, context);
|
options.onSuccess(data, variables, context);
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { Pad } from '@/features/pads';
|
import { Doc } from '@/features/docs';
|
||||||
|
|
||||||
export type UpdatePadParams = Pick<Pad, 'id'> &
|
export type UpdateDocParams = Pick<Doc, 'id'> &
|
||||||
Partial<Pick<Pad, 'content' | 'title' | 'is_public'>>;
|
Partial<Pick<Doc, 'content' | 'title' | 'is_public'>>;
|
||||||
|
|
||||||
export const updatePad = async ({
|
export const updateDoc = async ({
|
||||||
id,
|
id,
|
||||||
...params
|
...params
|
||||||
}: UpdatePadParams): Promise<Pad> => {
|
}: UpdateDocParams): Promise<Doc> => {
|
||||||
const response = await fetchAPI(`documents/${id}/`, {
|
const response = await fetchAPI(`documents/${id}/`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -18,24 +18,24 @@ export const updatePad = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new APIError('Failed to update the pad', await errorCauses(response));
|
throw new APIError('Failed to update the doc', await errorCauses(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json() as Promise<Pad>;
|
return response.json() as Promise<Doc>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UpdatePadProps {
|
interface UpdateDocProps {
|
||||||
onSuccess?: (data: Pad) => void;
|
onSuccess?: (data: Doc) => void;
|
||||||
listInvalideQueries?: string[];
|
listInvalideQueries?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUpdatePad({
|
export function useUpdateDoc({
|
||||||
onSuccess,
|
onSuccess,
|
||||||
listInvalideQueries,
|
listInvalideQueries,
|
||||||
}: UpdatePadProps = {}) {
|
}: UpdateDocProps = {}) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<Pad, APIError, UpdatePadParams>({
|
return useMutation<Doc, APIError, UpdateDocParams>({
|
||||||
mutationFn: updatePad,
|
mutationFn: updateDoc,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
listInvalideQueries?.forEach((queryKey) => {
|
listInvalideQueries?.forEach((queryKey) => {
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 742 B After Width: | Height: | Size: 742 B |
@@ -7,25 +7,25 @@ import IconGroup from '@/assets/icons/icon-group2.svg';
|
|||||||
import { Box, Card, StyledLink, Text } from '@/components';
|
import { Box, Card, StyledLink, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
import { useCreatePad } from '../api/useCreatePad';
|
import { useCreateDoc } from '../api/useCreateDoc';
|
||||||
|
|
||||||
import { InputPadName } from './InputPadName';
|
import { InputDocName } from './InputDocName';
|
||||||
|
|
||||||
export const CardCreatePad = () => {
|
export const CardCreateDoc = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {
|
const {
|
||||||
mutate: createPad,
|
mutate: createDoc,
|
||||||
isError,
|
isError,
|
||||||
isPending,
|
isPending,
|
||||||
error,
|
error,
|
||||||
} = useCreatePad({
|
} = useCreateDoc({
|
||||||
onSuccess: (pad) => {
|
onSuccess: (doc) => {
|
||||||
router.push(`/docs/${pad.id}`);
|
router.push(`/docs/${doc.id}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [padName, setPadName] = useState('');
|
const [docName, setDocName] = useState('');
|
||||||
const [padPublic, setPadPublic] = useState(false);
|
const [docPublic, setDocPublic] = useState(false);
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,14 +49,14 @@ export const CardCreatePad = () => {
|
|||||||
{t('Name the document')}
|
{t('Name the document')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<InputPadName
|
<InputDocName
|
||||||
label={t('Document name')}
|
label={t('Document name')}
|
||||||
{...{ error, isError, isPending, setPadName }}
|
{...{ error, isError, isPending, setDocName }}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
label={t('Is it public ?')}
|
label={t('Is it public ?')}
|
||||||
labelSide="right"
|
labelSide="right"
|
||||||
onChange={() => setPadPublic(!padPublic)}
|
onChange={() => setDocPublic(!docPublic)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box $justify="space-between" $direction="row" $align="center">
|
<Box $justify="space-between" $direction="row" $align="center">
|
||||||
@@ -64,8 +64,8 @@ export const CardCreatePad = () => {
|
|||||||
<Button color="secondary">{t('Cancel')}</Button>
|
<Button color="secondary">{t('Cancel')}</Button>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => createPad({ title: padName, is_public: padPublic })}
|
onClick={() => createDoc({ title: docName, is_public: docPublic })}
|
||||||
disabled={!padName}
|
disabled={!docName}
|
||||||
>
|
>
|
||||||
{t('Create the document')}
|
{t('Create the document')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -4,23 +4,23 @@ import { useEffect, useState } from 'react';
|
|||||||
import { APIError } from '@/api';
|
import { APIError } from '@/api';
|
||||||
import { Box, TextErrors } from '@/components';
|
import { Box, TextErrors } from '@/components';
|
||||||
|
|
||||||
interface InputPadNameProps {
|
interface InputDocNameProps {
|
||||||
error: APIError | null;
|
error: APIError | null;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
setPadName: (newPadName: string) => void;
|
setDocName: (newDocName: string) => void;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputPadName = ({
|
export const InputDocName = ({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
error,
|
error,
|
||||||
isError,
|
isError,
|
||||||
isPending,
|
isPending,
|
||||||
label,
|
label,
|
||||||
setPadName,
|
setDocName,
|
||||||
}: InputPadNameProps) => {
|
}: InputDocNameProps) => {
|
||||||
const [isInputError, setIsInputError] = useState(isError);
|
const [isInputError, setIsInputError] = useState(isError);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,7 +37,7 @@ export const InputPadName = ({
|
|||||||
label={label}
|
label={label}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPadName(e.target.value);
|
setDocName(e.target.value);
|
||||||
setIsInputError(false);
|
setIsInputError(false);
|
||||||
}}
|
}}
|
||||||
rightIcon={<span className="material-icons">edit</span>}
|
rightIcon={<span className="material-icons">edit</span>}
|
||||||
@@ -12,26 +12,26 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
|
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
|
||||||
|
|
||||||
import { useRemovePad } from '../api/useRemovePad';
|
import { useRemoveDoc } from '../api/useRemoveDoc';
|
||||||
import IconPad from '../assets/icon-pad.svg';
|
import IconDoc from '../assets/icon-doc.svg';
|
||||||
import IconRemove from '../assets/icon-trash.svg';
|
import IconRemove from '../assets/icon-trash.svg';
|
||||||
import { Pad } from '../types';
|
import { Doc } from '../types';
|
||||||
|
|
||||||
interface ModalRemovePadProps {
|
interface ModalRemoveDocProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate: removePad,
|
mutate: removeDoc,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
} = useRemovePad({
|
} = useRemoveDoc({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
|
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
@@ -62,8 +62,8 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
removePad({
|
removeDoc({
|
||||||
padId: pad.id,
|
docId: doc.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -75,7 +75,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
|||||||
<Box $align="center" $gap="1rem">
|
<Box $align="center" $gap="1rem">
|
||||||
<IconRemove width={48} color={colorsTokens()['primary-text']} />
|
<IconRemove width={48} color={colorsTokens()['primary-text']} />
|
||||||
<Text as="h2" $size="h3" $margin="none">
|
<Text as="h2" $size="h3" $margin="none">
|
||||||
{t('Deleting the document "{{title}}"', { title: pad.title })}
|
{t('Deleting the document "{{title}}"', { title: doc.title })}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
|||||||
<Alert canClose={false} type={VariantType.WARNING}>
|
<Alert canClose={false} type={VariantType.WARNING}>
|
||||||
<Text>
|
<Text>
|
||||||
{t('Are you sure you want to delete the document "{{title}}"?', {
|
{t('Are you sure you want to delete the document "{{title}}"?', {
|
||||||
title: pad.title,
|
title: doc.title,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
@@ -106,7 +106,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
|||||||
$align="center"
|
$align="center"
|
||||||
$radius="2px"
|
$radius="2px"
|
||||||
>
|
>
|
||||||
<IconPad
|
<IconDoc
|
||||||
className="p-t"
|
className="p-t"
|
||||||
aria-label={t(`Document icon`)}
|
aria-label={t(`Document icon`)}
|
||||||
color={colorsTokens()['primary-500']}
|
color={colorsTokens()['primary-500']}
|
||||||
@@ -118,7 +118,7 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text $theme="primary" $weight="bold" $size="l">
|
<Text $theme="primary" $weight="bold" $size="l">
|
||||||
{pad.title}
|
{doc.title}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -13,38 +13,38 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
|
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
|
||||||
|
|
||||||
import { KEY_LIST_PAD, KEY_PAD } from '../api';
|
import { KEY_DOC, KEY_LIST_DOC } from '../api';
|
||||||
import { useUpdatePad } from '../api/useUpdatePad';
|
import { useUpdateDoc } from '../api/useUpdateDoc';
|
||||||
import IconEdit from '../assets/icon-edit.svg';
|
import IconEdit from '../assets/icon-edit.svg';
|
||||||
import { Pad } from '../types';
|
import { Doc } from '../types';
|
||||||
|
|
||||||
import { InputPadName } from './InputPadName';
|
import { InputDocName } from './InputDocName';
|
||||||
|
|
||||||
interface ModalUpdatePadProps {
|
interface ModalUpdateDocProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
|
export const ModalUpdateDoc = ({ onClose, doc }: ModalUpdateDocProps) => {
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const [title, setTitle] = useState(pad.title);
|
const [title, setTitle] = useState(doc.title);
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const [padPublic, setPadPublic] = useState(pad.is_public);
|
const [docPublic, setDocPublic] = useState(doc.is_public);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate: updatePad,
|
mutate: updateDoc,
|
||||||
isError,
|
isError,
|
||||||
isPending,
|
isPending,
|
||||||
error,
|
error,
|
||||||
} = useUpdatePad({
|
} = useUpdateDoc({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast(t('The document has been updated.'), VariantType.SUCCESS, {
|
toast(t('The document has been updated.'), VariantType.SUCCESS, {
|
||||||
duration: 4000,
|
duration: 4000,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
listInvalideQueries: [KEY_PAD, KEY_LIST_PAD],
|
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,10 +69,10 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updatePad({
|
updateDoc({
|
||||||
title,
|
title,
|
||||||
id: pad.id,
|
id: doc.id,
|
||||||
is_public: padPublic,
|
is_public: docPublic,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -85,7 +85,7 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
|
|||||||
<IconEdit width={48} color={colorsTokens()['primary-text']} />
|
<IconEdit width={48} color={colorsTokens()['primary-text']} />
|
||||||
<Text as="h2" $size="h3" $margin="none">
|
<Text as="h2" $size="h3" $margin="none">
|
||||||
{t('Update document "{{documentTitle}}"', {
|
{t('Update document "{{documentTitle}}"', {
|
||||||
documentTitle: pad.title,
|
documentTitle: doc.title,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -101,16 +101,16 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Box $gap="1rem">
|
<Box $gap="1rem">
|
||||||
<InputPadName
|
<InputDocName
|
||||||
label={t('Document name')}
|
label={t('Document name')}
|
||||||
defaultValue={title}
|
defaultValue={title}
|
||||||
{...{ error, isError, isPending, setPadName: setTitle }}
|
{...{ error, isError, isPending, setDocName: setTitle }}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
label={t('Is it public ?')}
|
label={t('Is it public ?')}
|
||||||
labelSide="right"
|
labelSide="right"
|
||||||
defaultChecked={padPublic}
|
defaultChecked={docPublic}
|
||||||
onChange={() => setPadPublic(!padPublic)}
|
onChange={() => setDocPublic(!docPublic)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './CardCreateDoc';
|
||||||
|
export * from './ModalRemoveDoc';
|
||||||
|
export * from './ModalUpdateDoc';
|
||||||
@@ -22,7 +22,7 @@ export enum Role {
|
|||||||
|
|
||||||
export type Base64 = string;
|
export type Base64 = string;
|
||||||
|
|
||||||
export interface Pad {
|
export interface Doc {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
content: Base64;
|
content: Base64;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Pad, Role } from './types';
|
import { Doc, Role } from './types';
|
||||||
|
|
||||||
export const currentDocRole = (doc: Pad): Role => {
|
export const currentDocRole = (doc: Doc): Role => {
|
||||||
return doc.abilities.destroy
|
return doc.abilities.destroy
|
||||||
? Role.OWNER
|
? Role.OWNER
|
||||||
: doc.abilities.manage_accesses
|
: doc.abilities.manage_accesses
|
||||||
@@ -3,24 +3,24 @@ import React, { useMemo, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, DropButton, IconOptions, Text } from '@/components';
|
import { Box, DropButton, IconOptions, Text } from '@/components';
|
||||||
import { ModalAddMembers } from '@/features/pads/members/members-add';
|
|
||||||
import { ModalGridMembers } from '@/features/pads/members/members-grid/';
|
|
||||||
import {
|
import {
|
||||||
ModalRemovePad,
|
Doc,
|
||||||
ModalUpdatePad,
|
ModalRemoveDoc,
|
||||||
Pad,
|
ModalUpdateDoc,
|
||||||
currentDocRole,
|
currentDocRole,
|
||||||
} from '@/features/pads/pad-management';
|
} from '@/features/docs/doc-management';
|
||||||
|
import { ModalAddMembers } from '@/features/docs/members/members-add';
|
||||||
|
import { ModalGridMembers } from '@/features/docs/members/members-grid/';
|
||||||
|
|
||||||
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
|
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
|
||||||
|
|
||||||
import { ModalPDF } from './ModalPDF';
|
import { ModalPDF } from './ModalPDF';
|
||||||
|
|
||||||
interface PadToolBoxProps {
|
interface DocToolBoxProps {
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PadToolBox = ({ pad }: PadToolBoxProps) => {
|
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: templates } = useTemplates({
|
const { data: templates } = useTemplates({
|
||||||
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
|
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
|
||||||
@@ -62,7 +62,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
|
|||||||
isOpen={isDropOpen}
|
isOpen={isDropOpen}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
{pad.abilities.manage_accesses && (
|
{doc.abilities.manage_accesses && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -88,7 +88,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{pad.abilities.partial_update && (
|
{doc.abilities.partial_update && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalUpdateOpen(true);
|
setIsModalUpdateOpen(true);
|
||||||
@@ -101,7 +101,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
|
|||||||
<Text $theme="primary">{t('Update document')}</Text>
|
<Text $theme="primary">{t('Update document')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{pad.abilities.destroy && (
|
{doc.abilities.destroy && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalRemoveOpen(true);
|
setIsModalRemoveOpen(true);
|
||||||
@@ -130,28 +130,28 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
|
|||||||
{isModalGridMembersOpen && (
|
{isModalGridMembersOpen && (
|
||||||
<ModalGridMembers
|
<ModalGridMembers
|
||||||
onClose={() => setIsModalGridMembersOpen(false)}
|
onClose={() => setIsModalGridMembersOpen(false)}
|
||||||
doc={pad}
|
doc={doc}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isModalAddMembersOpen && (
|
{isModalAddMembersOpen && (
|
||||||
<ModalAddMembers
|
<ModalAddMembers
|
||||||
onClose={() => setIsModalAddMembersOpen(false)}
|
onClose={() => setIsModalAddMembersOpen(false)}
|
||||||
doc={pad}
|
doc={doc}
|
||||||
currentRole={currentDocRole(pad)}
|
currentRole={currentDocRole(doc)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isModalPDFOpen && (
|
{isModalPDFOpen && (
|
||||||
<ModalPDF
|
<ModalPDF
|
||||||
onClose={() => setIsModalPDFOpen(false)}
|
onClose={() => setIsModalPDFOpen(false)}
|
||||||
templateOptions={templateOptions}
|
templateOptions={templateOptions}
|
||||||
pad={pad}
|
doc={doc}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isModalUpdateOpen && (
|
{isModalUpdateOpen && (
|
||||||
<ModalUpdatePad onClose={() => setIsModalUpdateOpen(false)} pad={pad} />
|
<ModalUpdateDoc onClose={() => setIsModalUpdateOpen(false)} doc={doc} />
|
||||||
)}
|
)}
|
||||||
{isModalRemoveOpen && (
|
{isModalRemoveOpen && (
|
||||||
<ModalRemovePad onClose={() => setIsModalRemoveOpen(false)} pad={pad} />
|
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -12,8 +12,8 @@ import { t } from 'i18next';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { usePadStore } from '@/features/pads/pad-editor/';
|
import { useDocStore } from '@/features/docs/doc-editor/';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { useCreatePdf } from '../api/useCreatePdf';
|
import { useCreatePdf } from '../api/useCreatePdf';
|
||||||
import { adaptBlockNoteHTML, downloadFile } from '../utils';
|
import { adaptBlockNoteHTML, downloadFile } from '../utils';
|
||||||
@@ -24,12 +24,12 @@ interface ModalPDFProps {
|
|||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
}[];
|
}[];
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
|
export const ModalPDF = ({ onClose, templateOptions, doc }: ModalPDFProps) => {
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const { padsStore } = usePadStore();
|
const { docsStore } = useDocStore();
|
||||||
const {
|
const {
|
||||||
mutate: createPdf,
|
mutate: createPdf,
|
||||||
data: pdf,
|
data: pdf,
|
||||||
@@ -59,7 +59,7 @@ export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// normalize title
|
// normalize title
|
||||||
const title = pad.title
|
const title = doc.title
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize('NFD')
|
.normalize('NFD')
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
@@ -78,7 +78,7 @@ export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = padsStore[pad.id].editor;
|
const editor = docsStore[doc.id].editor;
|
||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
toast(t('No editor found'), VariantType.ERROR);
|
toast(t('No editor found'), VariantType.ERROR);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './DocToolBox';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Access } from '../pad-management';
|
import { Access } from '../doc-management';
|
||||||
|
|
||||||
export interface Template {
|
export interface Template {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -5,7 +5,7 @@ import fetchMock from 'fetch-mock';
|
|||||||
|
|
||||||
import { AppWrapper } from '@/tests/utils';
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
import { PadList } from '../components/PadList';
|
import { DocList } from '../components/DocList';
|
||||||
import { Panel } from '../components/Panel';
|
import { Panel } from '../components/Panel';
|
||||||
|
|
||||||
window.HTMLElement.prototype.scroll = function () {};
|
window.HTMLElement.prototype.scroll = function () {};
|
||||||
@@ -17,18 +17,18 @@ jest.mock('next/router', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('PanelPads', () => {
|
describe('PanelDocs', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
fetchMock.restore();
|
fetchMock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with no pad to display', async () => {
|
it('renders with no doc to display', async () => {
|
||||||
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
||||||
count: 0,
|
count: 0,
|
||||||
results: [],
|
results: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<PadList />, { wrapper: AppWrapper });
|
render(<DocList />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ describe('PanelPads', () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an empty pad', async () => {
|
it('renders an empty doc', async () => {
|
||||||
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
||||||
count: 1,
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
@@ -51,14 +51,14 @@ describe('PanelPads', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<PadList />, { wrapper: AppWrapper });
|
render(<DocList />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(await screen.findByLabelText('Empty pads icon')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Empty docs icon')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a pad with only 1 member', async () => {
|
it('renders a doc with only 1 member', async () => {
|
||||||
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
||||||
count: 1,
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
@@ -75,20 +75,20 @@ describe('PanelPads', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<PadList />, { wrapper: AppWrapper });
|
render(<DocList />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(await screen.findByLabelText('Empty pads icon')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Empty docs icon')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a non-empty pad', async () => {
|
it('renders a non-empty doc', async () => {
|
||||||
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
fetchMock.mock(`end:/documents/?page=1&ordering=-created_at`, {
|
||||||
count: 1,
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Pad 1',
|
name: 'Doc 1',
|
||||||
accesses: [
|
accesses: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -103,11 +103,11 @@ describe('PanelPads', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<PadList />, { wrapper: AppWrapper });
|
render(<DocList />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(await screen.findByLabelText('Pads icon')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Docs icon')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the error', async () => {
|
it('renders the error', async () => {
|
||||||
@@ -115,7 +115,7 @@ describe('PanelPads', () => {
|
|||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<PadList />, { wrapper: AppWrapper });
|
render(<DocList />, { wrapper: AppWrapper });
|
||||||
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
@@ -5,15 +5,15 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import IconGroup from '@/assets/icons/icon-group.svg';
|
import IconGroup from '@/assets/icons/icon-group.svg';
|
||||||
import { Box, StyledLink, Text } from '@/components';
|
import { Box, StyledLink, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import IconNone from '../assets/icon-none.svg';
|
import IconNone from '../assets/icon-none.svg';
|
||||||
|
|
||||||
interface PadItemProps {
|
interface DocItemProps {
|
||||||
pad: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PadItem = ({ pad }: PadItemProps) => {
|
export const DocItem = ({ doc }: DocItemProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const {
|
const {
|
||||||
@@ -21,8 +21,8 @@ export const PadItem = ({ pad }: PadItemProps) => {
|
|||||||
} = useRouter();
|
} = useRouter();
|
||||||
|
|
||||||
// There is at least 1 owner in the team
|
// There is at least 1 owner in the team
|
||||||
const hasMembers = pad.accesses.length > 1;
|
const hasMembers = doc.accesses.length > 1;
|
||||||
const isActive = pad.id === id;
|
const isActive = doc.id === id;
|
||||||
|
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
className: 'p-t',
|
className: 'p-t',
|
||||||
@@ -63,11 +63,11 @@ export const PadItem = ({ pad }: PadItemProps) => {
|
|||||||
${isActive ? activeStyle : hoverStyle}
|
${isActive ? activeStyle : hoverStyle}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<StyledLink className="p-s pt-t pb-t" href={`/docs/${pad.id}`}>
|
<StyledLink className="p-s pt-t pb-t" href={`/docs/${doc.id}`}>
|
||||||
<Box $align="center" $direction="row" $gap="0.5rem">
|
<Box $align="center" $direction="row" $gap="0.5rem">
|
||||||
{hasMembers ? (
|
{hasMembers ? (
|
||||||
<IconGroup
|
<IconGroup
|
||||||
aria-label={t(`Pads icon`)}
|
aria-label={t(`Docs icon`)}
|
||||||
color={colorsTokens()['primary-500']}
|
color={colorsTokens()['primary-500']}
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
style={{
|
style={{
|
||||||
@@ -77,7 +77,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IconNone
|
<IconNone
|
||||||
aria-label={t(`Empty pads icon`)}
|
aria-label={t(`Empty docs icon`)}
|
||||||
color={colorsTokens()['greyscale-500']}
|
color={colorsTokens()['greyscale-500']}
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
style={{
|
style={{
|
||||||
@@ -93,7 +93,7 @@ export const PadItem = ({ pad }: PadItemProps) => {
|
|||||||
min-width: 14rem;
|
min-width: 14rem;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{pad.title}
|
{doc.title}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
@@ -5,19 +5,19 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { APIError } from '@/api';
|
import { APIError } from '@/api';
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { InfiniteScroll } from '@/components/InfiniteScroll';
|
import { InfiniteScroll } from '@/components/InfiniteScroll';
|
||||||
import { Pad, usePads } from '@/features/pads/pad-management';
|
import { Doc, useDocs } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { usePadPanelStore } from '../store';
|
import { useDocPanelStore } from '../store';
|
||||||
|
|
||||||
import { PadItem } from './PadItem';
|
import { DocItem } from './DocItem';
|
||||||
|
|
||||||
interface PanelTeamsStateProps {
|
interface PanelTeamsStateProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: APIError<unknown> | null;
|
error: APIError<unknown> | null;
|
||||||
pads?: Pad[];
|
docs?: Doc[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
|
const DocListState = ({ isLoading, error, docs }: PanelTeamsStateProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -28,7 +28,7 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pads?.length && !error) {
|
if (!docs?.length && !error) {
|
||||||
return (
|
return (
|
||||||
<Box $justify="center" $margin="small">
|
<Box $justify="center" $margin="small">
|
||||||
<Text
|
<Text
|
||||||
@@ -50,7 +50,7 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pads?.map((pad) => <PadItem pad={pad} key={pad.id} />)}
|
{docs?.map((doc) => <DocItem doc={doc} key={doc.id} />)}
|
||||||
{error && (
|
{error && (
|
||||||
<Box
|
<Box
|
||||||
$justify="center"
|
$justify="center"
|
||||||
@@ -72,8 +72,8 @@ const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PadList = () => {
|
export const DocList = () => {
|
||||||
const ordering = usePadPanelStore((state) => state.ordering);
|
const ordering = useDocPanelStore((state) => state.ordering);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
error,
|
error,
|
||||||
@@ -81,14 +81,14 @@ export const PadList = () => {
|
|||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
} = usePads({
|
} = useDocs({
|
||||||
ordering,
|
ordering,
|
||||||
});
|
});
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const pads = useMemo(() => {
|
const docs = useMemo(() => {
|
||||||
return data?.pages.reduce((acc, page) => {
|
return data?.pages.reduce((acc, page) => {
|
||||||
return acc.concat(page.results);
|
return acc.concat(page.results);
|
||||||
}, [] as Pad[]);
|
}, [] as Doc[]);
|
||||||
}, [data?.pages]);
|
}, [data?.pages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -105,7 +105,7 @@ export const PadList = () => {
|
|||||||
$margin={{ top: 'none' }}
|
$margin={{ top: 'none' }}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
<PadListState isLoading={isLoading} error={error} pads={pads} />
|
<DocListState isLoading={isLoading} error={error} docs={docs} />
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Box, BoxButton, Text } from '@/components';
|
import { Box, BoxButton, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
import { PadList } from './PadList';
|
import { DocList } from './DocList';
|
||||||
import { PanelActions } from './PanelActions';
|
import { PanelActions } from './PanelActions';
|
||||||
|
|
||||||
export const Panel = () => {
|
export const Panel = () => {
|
||||||
@@ -80,7 +80,7 @@ export const Panel = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
<PanelActions />
|
<PanelActions />
|
||||||
</Box>
|
</Box>
|
||||||
<PadList />
|
<DocList />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -3,18 +3,18 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { Box, BoxButton, StyledLink } from '@/components';
|
import { Box, BoxButton, StyledLink } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { PadsOrdering } from '@/features/pads/pad-management';
|
import { DocsOrdering } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import IconAdd from '../assets/icon-add.svg';
|
import IconAdd from '../assets/icon-add.svg';
|
||||||
import IconSort from '../assets/icon-sort.svg';
|
import IconSort from '../assets/icon-sort.svg';
|
||||||
import { usePadPanelStore } from '../store';
|
import { useDocPanelStore } from '../store';
|
||||||
|
|
||||||
export const PanelActions = () => {
|
export const PanelActions = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { changeOrdering, ordering } = usePadPanelStore();
|
const { changeOrdering, ordering } = useDocPanelStore();
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
const isSortAsc = ordering === PadsOrdering.BY_CREATED_ON;
|
const isSortAsc = ordering === DocsOrdering.BY_CREATED_ON;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './useDocPanelStore';
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
import { DocsOrdering } from '@/features/docs/doc-management/api';
|
||||||
|
|
||||||
|
interface DocPanelStore {
|
||||||
|
ordering: DocsOrdering;
|
||||||
|
changeOrdering: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDocPanelStore = create<DocPanelStore>((set) => ({
|
||||||
|
ordering: DocsOrdering.BY_CREATED_ON_DESC,
|
||||||
|
changeOrdering: () =>
|
||||||
|
set(({ ordering }) => ({
|
||||||
|
ordering:
|
||||||
|
ordering === DocsOrdering.BY_CREATED_ON
|
||||||
|
? DocsOrdering.BY_CREATED_ON_DESC
|
||||||
|
: DocsOrdering.BY_CREATED_ON,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
3
src/frontend/apps/impress/src/features/docs/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './doc-editor';
|
||||||
|
export * from './doc-management';
|
||||||
|
export * from './docs-panel';
|
||||||
@@ -2,13 +2,13 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { User } from '@/core/auth';
|
import { User } from '@/core/auth';
|
||||||
import { KEY_LIST_DOC_ACCESSES } from '@/features/pads/members/members-grid/';
|
|
||||||
import {
|
import {
|
||||||
Access,
|
Access,
|
||||||
KEY_LIST_PAD,
|
Doc,
|
||||||
Pad,
|
KEY_LIST_DOC,
|
||||||
Role,
|
Role,
|
||||||
} from '@/features/pads/pad-management';
|
} from '@/features/docs/doc-management';
|
||||||
|
import { KEY_LIST_DOC_ACCESSES } from '@/features/docs/members/members-grid/';
|
||||||
|
|
||||||
import { OptionType } from '../types';
|
import { OptionType } from '../types';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import { KEY_LIST_USER } from './useUsers';
|
|||||||
|
|
||||||
interface CreateDocAccessParams {
|
interface CreateDocAccessParams {
|
||||||
role: Role;
|
role: Role;
|
||||||
docId: Pad['id'];
|
docId: Doc['id'];
|
||||||
memberId: User['id'];
|
memberId: User['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export function useCreateDocAccess() {
|
|||||||
mutationFn: createDocAccess,
|
mutationFn: createDocAccess,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_LIST_PAD],
|
queryKey: [KEY_LIST_DOC],
|
||||||
});
|
});
|
||||||
void queryClient.resetQueries({
|
void queryClient.resetQueries({
|
||||||
queryKey: [KEY_LIST_USER],
|
queryKey: [KEY_LIST_USER],
|
||||||
@@ -2,14 +2,14 @@ import { useMutation } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { User } from '@/core/auth';
|
import { User } from '@/core/auth';
|
||||||
import { Pad, Role } from '@/features/pads/pad-management';
|
import { Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { DocInvitation, OptionType } from '../types';
|
import { DocInvitation, OptionType } from '../types';
|
||||||
|
|
||||||
interface CreateDocInvitationParams {
|
interface CreateDocInvitationParams {
|
||||||
email: User['email'];
|
email: User['email'];
|
||||||
role: Role;
|
role: Role;
|
||||||
docId: Pad['id'];
|
docId: Doc['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createDocInvitation = async ({
|
export const createDocInvitation = async ({
|
||||||
@@ -2,11 +2,11 @@ import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
||||||
import { User } from '@/core/auth';
|
import { User } from '@/core/auth';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export type UsersParams = {
|
export type UsersParams = {
|
||||||
query: string;
|
query: string;
|
||||||
docId: Pad['id'];
|
docId: Doc['id'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type UsersResponse = APIList<User>;
|
type UsersResponse = APIList<User>;
|
||||||
|
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 925 B |
@@ -1,7 +1,7 @@
|
|||||||
import { Radio, RadioGroup } from '@openfun/cunningham-react';
|
import { Radio, RadioGroup } from '@openfun/cunningham-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Role } from '@/features/pads/pad-management';
|
import { Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
interface ChooseRoleProps {
|
interface ChooseRoleProps {
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
@@ -12,7 +12,7 @@ import { createGlobalStyle } from 'styled-components';
|
|||||||
import { APIError } from '@/api';
|
import { APIError } from '@/api';
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Pad, Role } from '@/features/pads/pad-management';
|
import { Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { useCreateDocAccess, useCreateInvitation } from '../api';
|
import { useCreateDocAccess, useCreateInvitation } from '../api';
|
||||||
import IconAddUser from '../assets/add-user.svg';
|
import IconAddUser from '../assets/add-user.svg';
|
||||||
@@ -41,7 +41,7 @@ type APIErrorUser = APIError<{
|
|||||||
interface ModalAddMembersProps {
|
interface ModalAddMembersProps {
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalAddMembers = ({
|
export const ModalAddMembers = ({
|
||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Options } from 'react-select';
|
import { Options } from 'react-select';
|
||||||
import AsyncSelect from 'react-select/async';
|
import AsyncSelect from 'react-select/async';
|
||||||
|
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { isValidEmail } from '@/utils';
|
import { isValidEmail } from '@/utils';
|
||||||
|
|
||||||
import { KEY_LIST_USER, useUsers } from '../api/useUsers';
|
import { KEY_LIST_USER, useUsers } from '../api/useUsers';
|
||||||
@@ -12,7 +12,7 @@ import { OptionSelect, OptionType } from '../types';
|
|||||||
export type OptionsSelect = Options<OptionSelect>;
|
export type OptionsSelect = Options<OptionSelect>;
|
||||||
|
|
||||||
interface SearchUsersProps {
|
interface SearchUsersProps {
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
selectedUsers: OptionsSelect;
|
selectedUsers: OptionsSelect;
|
||||||
setSelectedUsers: (value: OptionsSelect) => void;
|
setSelectedUsers: (value: OptionsSelect) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { User } from '@/core/auth';
|
import { User } from '@/core/auth';
|
||||||
import { Pad, Role } from '@/features/pads/pad-management';
|
import { Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export enum OptionType {
|
export enum OptionType {
|
||||||
INVITATION = 'invitation',
|
INVITATION = 'invitation',
|
||||||
@@ -30,7 +30,7 @@ export interface DocInvitation {
|
|||||||
id: string;
|
id: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
email: string;
|
email: string;
|
||||||
team: Pad['id'];
|
team: Doc['id'];
|
||||||
role: Role;
|
role: Role;
|
||||||
issuer: User['id'];
|
issuer: User['id'];
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
import { Access, Doc, Role } from '@/features/docs/doc-management';
|
||||||
import { AppWrapper } from '@/tests/utils';
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
import { MemberAction } from '../components/MemberAction';
|
import { MemberAction } from '../components/MemberAction';
|
||||||
@@ -23,7 +23,7 @@ const access: Access = {
|
|||||||
const doc = {
|
const doc = {
|
||||||
id: '123456',
|
id: '123456',
|
||||||
title: 'teamName',
|
title: 'teamName',
|
||||||
} as Pad;
|
} as Doc;
|
||||||
|
|
||||||
describe('MemberAction', () => {
|
describe('MemberAction', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -3,12 +3,12 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
import { Access, Doc, Role } from '@/features/docs/doc-management';
|
||||||
import { AppWrapper } from '@/tests/utils';
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
import { MemberGrid } from '../components/MemberGrid';
|
import { MemberGrid } from '../components/MemberGrid';
|
||||||
|
|
||||||
const doc: Pad = {
|
const doc: Doc = {
|
||||||
id: '123456',
|
id: '123456',
|
||||||
title: 'teamName',
|
title: 'teamName',
|
||||||
abilities: {
|
abilities: {
|
||||||
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
|
|||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import { useAuthStore } from '@/core/auth';
|
import { useAuthStore } from '@/core/auth';
|
||||||
import { Access, Role } from '@/features/pads/pad-management';
|
import { Access, Role } from '@/features/docs/doc-management';
|
||||||
import { AppWrapper } from '@/tests/utils';
|
import { AppWrapper } from '@/tests/utils';
|
||||||
|
|
||||||
import { ModalRole } from '../components/ModalRole';
|
import { ModalRole } from '../components/ModalRole';
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { KEY_LIST_PAD, KEY_PAD } from '@/features/pads/pad-management';
|
import { KEY_DOC, KEY_LIST_DOC } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
|
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
|
||||||
|
|
||||||
@@ -46,10 +46,10 @@ export const useDeleteDocAccess = (options?: UseDeleteDocAccessOptions) => {
|
|||||||
queryKey: [KEY_LIST_DOC_ACCESSES],
|
queryKey: [KEY_LIST_DOC_ACCESSES],
|
||||||
});
|
});
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_PAD],
|
queryKey: [KEY_DOC],
|
||||||
});
|
});
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_LIST_PAD],
|
queryKey: [KEY_LIST_DOC],
|
||||||
});
|
});
|
||||||
if (options?.onSuccess) {
|
if (options?.onSuccess) {
|
||||||
options.onSuccess(data, variables, context);
|
options.onSuccess(data, variables, context);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
import { APIError, APIList, errorCauses, fetchAPI } from '@/api';
|
||||||
import { Access } from '@/features/pads/pad-management';
|
import { Access } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export type DocAccessesAPIParams = {
|
export type DocAccessesAPIParams = {
|
||||||
page: number;
|
page: number;
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||||
import { Access, KEY_PAD, Role } from '@/features/pads/pad-management';
|
import { Access, KEY_DOC, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
|
import { KEY_LIST_DOC_ACCESSES } from './useDocAccesses';
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export const useUpdateDocAccess = (options?: UseUpdateDocAccessOptions) => {
|
|||||||
queryKey: [KEY_LIST_DOC_ACCESSES],
|
queryKey: [KEY_LIST_DOC_ACCESSES],
|
||||||
});
|
});
|
||||||
void queryClient.invalidateQueries({
|
void queryClient.invalidateQueries({
|
||||||
queryKey: [KEY_PAD],
|
queryKey: [KEY_DOC],
|
||||||
});
|
});
|
||||||
if (options?.onSuccess) {
|
if (options?.onSuccess) {
|
||||||
options.onSuccess(data, variables, context);
|
options.onSuccess(data, variables, context);
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, DropButton, IconOptions, Text } from '@/components';
|
import { Box, DropButton, IconOptions, Text } from '@/components';
|
||||||
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
import { Access, Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { ModalDelete } from './ModalDelete';
|
import { ModalDelete } from './ModalDelete';
|
||||||
import { ModalRole } from './ModalRole';
|
import { ModalRole } from './ModalRole';
|
||||||
@@ -11,7 +11,7 @@ import { ModalRole } from './ModalRole';
|
|||||||
interface MemberActionProps {
|
interface MemberActionProps {
|
||||||
access: Access;
|
access: Access;
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MemberAction = ({
|
export const MemberAction = ({
|
||||||
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Card, TextErrors } from '@/components';
|
import { Card, TextErrors } from '@/components';
|
||||||
import { Pad, Role, currentDocRole } from '@/features/pads/pad-management';
|
import { Doc, Role, currentDocRole } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { useDocAccesses } from '../api';
|
import { useDocAccesses } from '../api';
|
||||||
import { PAGE_SIZE } from '../conf';
|
import { PAGE_SIZE } from '../conf';
|
||||||
@@ -11,7 +11,7 @@ import { PAGE_SIZE } from '../conf';
|
|||||||
import { MemberAction } from './MemberAction';
|
import { MemberAction } from './MemberAction';
|
||||||
|
|
||||||
interface MemberGridProps {
|
interface MemberGridProps {
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME : ask Cunningham to export this type
|
// FIXME : ask Cunningham to export this type
|
||||||
@@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import IconUser from '@/assets/icons/icon-user.svg';
|
import IconUser from '@/assets/icons/icon-user.svg';
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Access, Pad, Role } from '@/features/pads/pad-management';
|
import { Access, Doc, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { useDeleteDocAccess } from '../api';
|
import { useDeleteDocAccess } from '../api';
|
||||||
import IconRemoveMember from '../assets/icon-remove-member.svg';
|
import IconRemoveMember from '../assets/icon-remove-member.svg';
|
||||||
@@ -22,7 +22,7 @@ interface ModalDeleteProps {
|
|||||||
access: Access;
|
access: Access;
|
||||||
currentRole: Role;
|
currentRole: Role;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {
|
export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {
|
||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { Pad } from '@/features/pads/pad-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { MemberGrid } from './MemberGrid';
|
import { MemberGrid } from './MemberGrid';
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
|
|
||||||
interface ModalGridMembersProps {
|
interface ModalGridMembersProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
doc: Pad;
|
doc: Doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalGridMembers = ({ doc, onClose }: ModalGridMembersProps) => {
|
export const ModalGridMembers = ({ doc, onClose }: ModalGridMembersProps) => {
|
||||||
@@ -10,7 +10,7 @@ import { useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, Text, TextErrors } from '@/components';
|
import { Box, Text, TextErrors } from '@/components';
|
||||||
import { Access, Role } from '@/features/pads/pad-management';
|
import { Access, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
import { ChooseRole } from '../../members-add/components/ChooseRole';
|
import { ChooseRole } from '../../members-add/components/ChooseRole';
|
||||||
import { useUpdateDocAccess } from '../api';
|
import { useUpdateDocAccess } from '../api';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useAuthStore } from '@/core/auth';
|
import { useAuthStore } from '@/core/auth';
|
||||||
import { Access, Role } from '@/features/pads/pad-management';
|
import { Access, Role } from '@/features/docs/doc-management';
|
||||||
|
|
||||||
export const useWhoAmI = (access: Access) => {
|
export const useWhoAmI = (access: Access) => {
|
||||||
const { userData } = useAuthStore();
|
const { userData } = useAuthStore();
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './pad-editor';
|
|
||||||
export * from './pad-management';
|
|
||||||
export * from './pads-panel';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './PadEditor';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './usePadStore';
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { BlockNoteEditor } from '@blocknote/core';
|
|
||||||
import { WebrtcProvider } from 'y-webrtc';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
import { signalingUrl } from '@/core';
|
|
||||||
import { Base64, Pad } from '@/features/pads/pad-management';
|
|
||||||
|
|
||||||
export interface PadStore {
|
|
||||||
padsStore: {
|
|
||||||
[padId: Pad['id']]: {
|
|
||||||
provider: WebrtcProvider;
|
|
||||||
editor?: BlockNoteEditor;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
createProvider: (padId: Pad['id'], initialDoc: Base64) => WebrtcProvider;
|
|
||||||
setEditor: (padId: Pad['id'], editor: BlockNoteEditor) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
padsStore: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePadStore = create<PadStore>((set) => ({
|
|
||||||
padsStore: initialState.padsStore,
|
|
||||||
createProvider: (padId: string, initialDoc: Base64) => {
|
|
||||||
const doc = new Y.Doc({
|
|
||||||
guid: padId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (initialDoc) {
|
|
||||||
Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = new WebrtcProvider(padId, doc, {
|
|
||||||
signaling: [signalingUrl(padId)],
|
|
||||||
maxConns: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
set(({ padsStore }) => {
|
|
||||||
return {
|
|
||||||
padsStore: {
|
|
||||||
...padsStore,
|
|
||||||
[padId]: {
|
|
||||||
provider,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return provider;
|
|
||||||
},
|
|
||||||
setEditor: (padId, editor) => {
|
|
||||||
set(({ padsStore }) => {
|
|
||||||
return {
|
|
||||||
padsStore: {
|
|
||||||
...padsStore,
|
|
||||||
[padId]: {
|
|
||||||
...padsStore[padId],
|
|
||||||
editor,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './usePad';
|
|
||||||
export * from './usePads';
|
|
||||||
export * from './useUpdatePad';
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
|
||||||
import { KEY_LIST_PAD, Pad } from '@/features/pads';
|
|
||||||
|
|
||||||
type CreatePadParam = Pick<Pad, 'title' | 'is_public'>;
|
|
||||||
|
|
||||||
export const createPad = async ({
|
|
||||||
title,
|
|
||||||
is_public,
|
|
||||||
}: CreatePadParam): Promise<Pad> => {
|
|
||||||
const response = await fetchAPI(`documents/`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
title,
|
|
||||||
is_public,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new APIError('Failed to create the pad', await errorCauses(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json() as Promise<Pad>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CreatePadProps {
|
|
||||||
onSuccess: (data: Pad) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCreatePad({ onSuccess }: CreatePadProps) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation<Pad, APIError, CreatePadParam>({
|
|
||||||
mutationFn: createPad,
|
|
||||||
onSuccess: (data) => {
|
|
||||||
void queryClient.invalidateQueries({
|
|
||||||
queryKey: [KEY_LIST_PAD],
|
|
||||||
});
|
|
||||||
onSuccess(data);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
|
||||||
|
|
||||||
import { Pad } from '../types';
|
|
||||||
|
|
||||||
export type PadParams = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPad = async ({ id }: PadParams): Promise<Pad> => {
|
|
||||||
const response = await fetchAPI(`documents/${id}/`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new APIError('Failed to get the pad', await errorCauses(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json() as Promise<Pad>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const KEY_PAD = 'pad';
|
|
||||||
|
|
||||||
export function usePad(
|
|
||||||
param: PadParams,
|
|
||||||
queryConfig?: UseQueryOptions<Pad, APIError, Pad>,
|
|
||||||
) {
|
|
||||||
return useQuery<Pad, APIError, Pad>({
|
|
||||||
queryKey: [KEY_PAD, param],
|
|
||||||
queryFn: () => getPad(param),
|
|
||||||
...queryConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './CardCreatePad';
|
|
||||||
export * from './ModalRemovePad';
|
|
||||||
export * from './ModalUpdatePad';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './PadToolBox';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './usePadPanelStore';
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
import { PadsOrdering } from '@/features/pads/pad-management/api';
|
|
||||||
|
|
||||||
interface PadPanelStore {
|
|
||||||
ordering: PadsOrdering;
|
|
||||||
changeOrdering: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePadPanelStore = create<PadPanelStore>((set) => ({
|
|
||||||
ordering: PadsOrdering.BY_CREATED_ON_DESC,
|
|
||||||
changeOrdering: () =>
|
|
||||||
set(({ ordering }) => ({
|
|
||||||
ordering:
|
|
||||||
ordering === PadsOrdering.BY_CREATED_ON
|
|
||||||
? PadsOrdering.BY_CREATED_ON_DESC
|
|
||||||
: PadsOrdering.BY_CREATED_ON,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"Docs": "Docs",
|
"Docs": "Docs",
|
||||||
"Docs Description": "Description de Docs",
|
"Docs Description": "Description de Docs",
|
||||||
"Docs Logo": "Logo Docs",
|
"Docs Logo": "Logo Docs",
|
||||||
|
"Docs icon": "Icône Docs",
|
||||||
"Document icon": "Icône du document",
|
"Document icon": "Icône du document",
|
||||||
"Document name": "Nom du document",
|
"Document name": "Nom du document",
|
||||||
"Documents": "Documents",
|
"Documents": "Documents",
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
"E-mail:": "E-mail:",
|
"E-mail:": "E-mail:",
|
||||||
"Editor": "Éditeur",
|
"Editor": "Éditeur",
|
||||||
"Emails": "Emails",
|
"Emails": "Emails",
|
||||||
"Empty pads icon": "Icône des pads vides",
|
"Empty docs icon": "Icône de docs vide",
|
||||||
"Enter the new name of the selected document.": "Entrez le nouveau nom du document sélectionné.",
|
"Enter the new name of the selected document.": "Entrez le nouveau nom du document sélectionné.",
|
||||||
"Established on December 20, 2023.": "Établi le 20 décembre 2023.",
|
"Established on December 20, 2023.": "Établi le 20 décembre 2023.",
|
||||||
"Failed to add the member in the document.": "Impossible d'ajouter le membre dans le document.",
|
"Failed to add the member in the document.": "Impossible d'ajouter le membre dans le document.",
|
||||||
@@ -84,7 +85,6 @@
|
|||||||
"Open the modal to update the role of this member": "Ouvrir la fenêtre modale pour mettre à jour le rôle de ce membre",
|
"Open the modal to update the role of this member": "Ouvrir la fenêtre modale pour mettre à jour le rôle de ce membre",
|
||||||
"Ouch !": "Aïe !",
|
"Ouch !": "Aïe !",
|
||||||
"Owner": "Propriétaire",
|
"Owner": "Propriétaire",
|
||||||
"Pads icon": "Icône de pads",
|
|
||||||
"Personal data and cookies": "Données personnelles et cookies",
|
"Personal data and cookies": "Données personnelles et cookies",
|
||||||
"Publication Director": "Directeur de la publication",
|
"Publication Director": "Directeur de la publication",
|
||||||
"Publisher": "Éditeur",
|
"Publisher": "Éditeur",
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { PropsWithChildren } from 'react';
|
|||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Panel } from '@/features/pads/pads-panel';
|
import { Panel } from '@/features/docs/docs-panel';
|
||||||
|
|
||||||
import { MainLayout } from './MainLayout';
|
import { MainLayout } from './MainLayout';
|
||||||
|
|
||||||
export function PadLayout({ children }: PropsWithChildren) {
|
export function DocLayout({ children }: PropsWithChildren) {
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './MainLayout';
|
export * from './MainLayout';
|
||||||
export * from './PadLayout';
|
export * from './DocLayout';
|
||||||
export * from './PageLayout';
|
export * from './PageLayout';
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useRouter } from 'next/router';
|
|||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
import { Box, Text, TextErrors } from '@/components/';
|
import { Box, Text, TextErrors } from '@/components/';
|
||||||
import { PadEditor } from '@/features/pads/pad-editor';
|
import { DocEditor } from '@/features/docs/doc-editor';
|
||||||
import { usePad } from '@/features/pads/pad-management';
|
import { useDoc } from '@/features/docs/doc-management';
|
||||||
import { PadLayout } from '@/layouts';
|
import { DocLayout } from '@/layouts';
|
||||||
import { NextPageWithLayout } from '@/types/next';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
@@ -18,15 +18,15 @@ const Page: NextPageWithLayout = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Pad id={id} />;
|
return <Doc id={id} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PadProps {
|
interface DocProps {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Pad = ({ id }: PadProps) => {
|
const Doc = ({ id }: DocProps) => {
|
||||||
const { data: pad, isLoading, isError, error } = usePad({ id });
|
const { data: doc, isLoading, isError, error } = useDoc({ id });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (isError && error) {
|
if (isError && error) {
|
||||||
@@ -51,7 +51,7 @@ const Pad = ({ id }: PadProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading || !pad) {
|
if (isLoading || !doc) {
|
||||||
return (
|
return (
|
||||||
<Box $align="center" $justify="center" $height="100%">
|
<Box $align="center" $justify="center" $height="100%">
|
||||||
<Loader />
|
<Loader />
|
||||||
@@ -59,11 +59,11 @@ const Pad = ({ id }: PadProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PadEditor pad={pad} />;
|
return <DocEditor doc={doc} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
Page.getLayout = function getLayout(page: ReactElement) {
|
Page.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <PadLayout>{page}</PadLayout>;
|
return <DocLayout>{page}</DocLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { CardCreatePad } from '@/features/pads/pad-management';
|
import { CardCreateDoc } from '@/features/docs/doc-management';
|
||||||
import { PadLayout } from '@/layouts';
|
import { DocLayout } from '@/layouts';
|
||||||
import { NextPageWithLayout } from '@/types/next';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
return (
|
return (
|
||||||
<Box $padding="large" $justify="center" $align="start" $height="inherit">
|
<Box $padding="large" $justify="center" $align="start" $height="inherit">
|
||||||
<CardCreatePad />
|
<CardCreateDoc />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Page.getLayout = function getLayout(page: ReactElement) {
|
Page.getLayout = function getLayout(page: ReactElement) {
|
||||||
return <PadLayout>{page}</PadLayout>;
|
return <DocLayout>{page}</DocLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||