✨(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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user