(frontend) increase doc visibility options

We now have 3 visibility options for docs:
- public
- restricted
- authenticated

We also have 2 editability options:
- readonly
- editable

The editability options are only available
for public and authenticated docs.
This commit is contained in:
Anthony LC
2024-10-23 11:11:29 +02:00
committed by Anthony LC
parent c0cb12f002
commit ff364f8b3d
13 changed files with 619 additions and 127 deletions

View File

@@ -358,6 +358,8 @@ const config = {
},
'forms-field': {
color: 'var(--c--theme--colors--primary-text)',
'footer-font-size': 'var(--c--theme--font--sizes--t)',
'footer-color': 'var(--c--theme--colors--greyscale-text)',
},
'forms-input': {
'border-radius': '4px',
@@ -372,6 +374,9 @@ const config = {
big: 'var(--c--theme--colors--primary-text)',
},
},
'forms-radio': {
'accent-color': 'var(--c--theme--colors--primary-600)',
},
'forms-select': {
'item-font-size': '14px',
'border-radius': '4px',

View File

@@ -16,6 +16,12 @@
line-height: initial;
}
.c__field .c__field__footer {
padding: 2px 0 0;
font-size: var(--c--components--forms-field--footer-font-size);
color: var(--c--components--forms-field--footer-color);
}
.labelled-box label {
color: var(--c--theme--colors--primary-text);
}
@@ -328,6 +334,10 @@ input:-webkit-autofill:focus {
cursor: not-allowed;
}
.c__checkbox.c__checkbox--disabled .c__checkbox__label {
color: var(--c--theme--colors--greyscale-400);
}
/**
* Button
*/

View File

@@ -477,6 +477,12 @@
--c--components--forms-datepicker--border-radius: 0;
--c--components--forms-fileuploader--border-radius: 0;
--c--components--forms-field--color: var(--c--theme--colors--primary-text);
--c--components--forms-field--footer-font-size: var(
--c--theme--font--sizes--t
);
--c--components--forms-field--footer-color: var(
--c--theme--colors--greyscale-text
);
--c--components--forms-input--border-radius: 4px;
--c--components--forms-input--background-color: #fff;
--c--components--forms-input--border-color: var(
@@ -492,6 +498,9 @@
--c--components--forms-labelledbox--label-color--big: var(
--c--theme--colors--primary-text
);
--c--components--forms-radio--accent-color: var(
--c--theme--colors--primary-600
);
--c--components--forms-select--item-font-size: 14px;
--c--components--forms-select--border-radius: 4px;
--c--components--forms-select--border-radius-hover: 4px;

View File

@@ -479,7 +479,11 @@ export const tokens = {
},
'forms-datepicker': { 'border-radius': '0' },
'forms-fileuploader': { 'border-radius': '0' },
'forms-field': { color: 'var(--c--theme--colors--primary-text)' },
'forms-field': {
color: 'var(--c--theme--colors--primary-text)',
'footer-font-size': 'var(--c--theme--font--sizes--t)',
'footer-color': 'var(--c--theme--colors--greyscale-text)',
},
'forms-input': {
'border-radius': '4px',
'background-color': '#ffffff',
@@ -491,6 +495,9 @@ export const tokens = {
'forms-labelledbox': {
'label-color': { big: 'var(--c--theme--colors--primary-text)' },
},
'forms-radio': {
'accent-color': 'var(--c--theme--colors--primary-600)',
},
'forms-select': {
'item-font-size': '14px',
'border-radius': '4px',

View File

@@ -1,16 +1,17 @@
import {
Button,
Switch,
Radio,
RadioGroup,
Select,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Card, IconBG } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { KEY_DOC, KEY_LIST_DOC, useUpdateDocLink } from '../api';
import { Doc, LinkReach } from '../types';
import { Doc, LinkReach, LinkRole } from '../types';
interface DocVisibilityProps {
doc: Doc;
@@ -18,14 +19,12 @@ interface DocVisibilityProps {
export const DocVisibility = ({ doc }: DocVisibilityProps) => {
const { t } = useTranslation();
const [docPublic, setDocPublic] = useState(
doc.link_reach === LinkReach.PUBLIC,
);
const { toast } = useToastProvider();
const { colorsTokens } = useCunninghamTheme();
const api = useUpdateDocLink({
onSuccess: () => {
toast(
t('The document visiblitity has been updated.'),
t('The document visibility has been updated.'),
VariantType.SUCCESS,
{
duration: 4000,
@@ -35,6 +34,34 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
});
const transLinkReach = {
[LinkReach.RESTRICTED]: {
label: t('Restricted'),
description: t('Only for people with access'),
},
[LinkReach.AUTHENTICATED]: {
label: t('Authenticated'),
description: t('Only for authenticated users'),
},
[LinkReach.PUBLIC]: {
label: t('Public'),
description: t('Anyone on the internet with the link can view'),
},
};
const linkRoleList = [
{
label: t('Read only'),
value: LinkRole.READER,
},
{
label: t('Can read and edit'),
value: LinkRole.EDITOR,
},
];
const showLinkRoleOptions = doc.link_reach !== LinkReach.RESTRICTED;
return (
<Card
$margin="tiny"
@@ -44,55 +71,73 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
$align="center"
$justify="space-between"
$gap="1rem"
$wrap="wrap"
>
<IconBG iconName="public" $margin="none" />
<IconBG iconName="public" />
<Box
$width="100%"
$wrap="wrap"
$gap="1rem"
$justify="space-between"
$direction="row"
$align="center"
$flex="1"
$css={`
& .c__field__footer .c__field__text {
${!doc.abilities.link_configuration && `color: ${colorsTokens()['greyscale-400']};`};
}
`}
>
<Box $direction="row" $gap="1rem" $align="center">
<Switch
label={t(docPublic ? 'Doc public' : 'Doc private')}
defaultChecked={docPublic}
onChange={() => {
<Box $shrink="0" $flex="auto" $maxWidth="20rem">
<Select
label={t('Visibility')}
options={Object.values(LinkReach).map((linkReach) => ({
label: transLinkReach[linkReach].label,
value: linkReach,
}))}
onChange={(evt) =>
api.mutate({
link_reach: evt.target.value as LinkReach,
id: doc.id,
link_reach: docPublic ? LinkReach.RESTRICTED : LinkReach.PUBLIC,
link_role: 'reader',
});
setDocPublic(!docPublic);
}}
disabled={!doc.abilities.link_configuration}
text={
docPublic
? t('Anyone on the internet with the link can view')
: t('Only for people with access')
})
}
value={doc.link_reach}
clearable={false}
text={transLinkReach[doc.link_reach].description}
disabled={!doc.abilities.link_configuration}
/>
</Box>
<Button
onClick={() => {
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast(t('Link Copied !'), VariantType.SUCCESS, {
duration: 3000,
});
})
.catch(() => {
toast(t('Failed to copy link'), VariantType.ERROR, {
duration: 3000,
});
});
}}
color="primary"
icon={<span className="material-icons">copy</span>}
>
{t('Copy link')}
</Button>
{showLinkRoleOptions && (
<Box
$css={`
& .c__checkbox{
padding: 0.15rem 0.25rem;
}
`}
>
<RadioGroup
compact
style={{
display: 'flex',
}}
text={t('How people can interact with the document')}
>
{linkRoleList.map((radio) => (
<Radio
key={radio.value}
label={radio.label}
value={radio.value}
onChange={() =>
api.mutate({
link_role: radio.value,
id: doc.id,
})
}
checked={doc.link_role === radio.value}
disabled={!doc.abilities.link_configuration}
/>
))}
</RadioGroup>
</Box>
)}
</Box>
</Card>
);

View File

@@ -1,3 +1,8 @@
import {
Button,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { t } from 'i18next';
import { createGlobalStyle } from 'styled-components';
@@ -41,6 +46,7 @@ interface ModalShareProps {
export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
const { isMobile, isSmallMobile } = useResponsiveStore();
const width = isSmallMobile ? '100vw' : isMobile ? '90vw' : '70vw';
const { toast } = useToastProvider();
return (
<>
@@ -68,13 +74,44 @@ export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
iconName="share"
$margin="none"
/>
<Box $align="flex-start">
<Text as="h3" $size="26px" $margin="none">
{t('Share')}
</Text>
<Text $size="small" $weight="normal" $textAlign="left">
{doc.title}
</Text>
<Box
$justify="space-between"
$direction="row"
$align="center"
$width="100%"
$gap="1rem"
$wrap="wrap"
>
<Box $align="flex-start">
<Text as="h3" $size="26px" $margin="none">
{t('Share')}
</Text>
<Text $size="small" $weight="normal" $textAlign="left">
{doc.title}
</Text>
</Box>
<Box $margin={{ right: '1.5rem' }} $shrink="0">
<Button
onClick={() => {
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast(t('Link Copied !'), VariantType.SUCCESS, {
duration: 3000,
});
})
.catch(() => {
toast(t('Failed to copy link'), VariantType.ERROR, {
duration: 3000,
});
});
}}
color="primary"
icon={<span className="material-icons">copy</span>}
>
{t('Copy link')}
</Button>
</Box>
</Box>
</Card>
<DocVisibility doc={doc} />

View File

@@ -27,6 +27,11 @@ export enum LinkReach {
AUTHENTICATED = 'authenticated',
}
export enum LinkRole {
READER = 'reader',
EDITOR = 'editor',
}
export type Base64 = string;
export interface Doc {
@@ -34,7 +39,7 @@ export interface Doc {
title: string;
content: Base64;
link_reach: LinkReach;
link_role: 'reader' | 'editor';
link_role: LinkRole;
accesses: Access[];
created_at: string;
updated_at: string;

View File

@@ -1,7 +1,11 @@
import { WorkboxPlugin } from 'workbox-core';
import { Doc, DocsResponse } from '@/features/docs/doc-management';
import { LinkReach, Role } from '@/features/docs/doc-management/types';
import {
LinkReach,
LinkRole,
Role,
} from '@/features/docs/doc-management/types';
import { DBRequest, DocsDB } from './DocsDB';
import { RequestSerializer } from './RequestSerializer';
@@ -220,7 +224,7 @@ export class ApiPlugin implements WorkboxPlugin {
},
],
link_reach: LinkReach.RESTRICTED,
link_role: 'reader',
link_role: LinkRole.READER,
};
await DocsDB.cacheResponse(