💄 front: WIP Implementation of the design for the share link portion (#50)

This commit is contained in:
Eric Doughty-Papassideris
2024-04-21 22:10:41 +02:00
committed by ericlinagora
parent e0fa40f77b
commit 3a0e0a9085
11 changed files with 561 additions and 167 deletions
+15 -3
View File
@@ -204,12 +204,24 @@
"components.public-link-get" : "Create link",
"components.public-link-copy" : "Copy link",
"components.public-link-access-level-create" : "Anyone with the link will have access to",
"components.public-link-access-level-update" : "Anyone with the link has access to",
"components.public-link-access-level-update-subtitle" : "Anyone with the link has access to",
"components.public-link-access-level-delete" : "Delete link",
"components.public-link-security": "Public link security",
"components.public-link-security-title": "Share with link",
"components.public-link-security": "Link security settings",
"components.public-link-security-change": "Change",
"components.public-link-security-change-subtitle": "Set password, expiration date, etc.",
"components.public-link-security_password": "Password",
"components.public-link-security_password_copied": "Password copied to clipboard",
"components.public-link-security_expired": "Expiration",
"components.public-link-security_password_removal_title": "About to remove password",
"components.public-link-security_password_removal_body": "Warning: about to remove the password on the public link making available to anyone knowing it",
"components.public-link-security_password_removal_confirm": "Remove password",
"components.public-link-security_expired": "Expired",
"components.public-link-security_field_confirm_edit": "Done",
"components.public-link-security_expiration_title": "Link expiration",
"components.public-link-security_expiration_removal_title": "About to remove expiry date of an expired link",
"components.public-link-security_expiration_removal_no_password": "Warning: about to remove the expiry on the public link making available to anyone knowing it",
"components.public-link-security_expiration_removal_but_password": "Warning: about to remove the expiry on the public link making available to anyone with the password",
"components.public-link-security_expiration_removal_confirm": "Remove expiration",
"components.internal-access_access_manage":"General access management",
"components.internal-access_inherit_parent":"Inherit parent folder",
"components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.",
+14 -1
View File
@@ -182,7 +182,20 @@
"components.public-link-security": "Sécurité du lien public",
"components.public-link-security_password": "Mot de passe",
"components.public-link-security_password_copied": "Mot de passe copié dans le presse-papiers",
"components.public-link-security_expired": "Expiration",
"components.public-link-security_expired": "Expiré",
"components.public-link-security_expiration_title": "Expiration du lien",
"components.public-link-security_expiration_removal_title": "À propos de la suppression de la date d'expiration d'un lien expiré",
"components.public-link-security_expiration_removal_no_password": "Attention : sur le point de supprimer l'expiration du lien public, rendant celui-ci accessible à quiconque le connaît",
"components.public-link-security_expiration_removal_but_password": "Attention : sur le point de supprimer l'expiration du lien public, rendant celui-ci accessible à quiconque possède le mot de passe",
"components.public-link-security_expiration_removal_confirm": "Supprimer l'expiration",
"components.public-link-security_field_confirm_edit": "Terminé",
"components.public-link-security_password_removal_title": "À propos de la suppression du mot de passe",
"components.public-link-security_password_removal_body": "Attention : sur le point de supprimer le mot de passe du lien public, le rendant accessible à quiconque le connaît",
"components.public-link-security_password_removal_confirm": "Supprimer le mot de passe",
"components.public-link-access-level-update-subtitle": "Toute personne disposant du lien y a accès à",
"components.public-link-security-change": "Modifier",
"components.public-link-security-change-subtitle": "Définir le mot de passe, la date d'expiration, etc.",
"components.public-link-security-title": "Partager avec un lien",
"components.internal-access_access_manage": "Gestion générale des accès",
"components.internal-access_inherit_parent": "Hériter du dossier parent",
"components.internal-access_inherit_parent_perm": "Hériter des permissions du dossier parent",
+14 -1
View File
@@ -179,10 +179,23 @@
"components.public-link-copied-info" : "Ссылка скопирована",
"components.public-link-get" : "Создать ссылку",
"components.public-link-copy" : "Копировать ссылку",
"components.public-link-security": "Public link security",
"components.public-link-security_password": "Password",
"components.public-link-security_password_copied": "Password copied to clipboard",
"components.public-link-security_expired": "Expiration",
"components.public-link-security_expiration_title": "Истечение срока действия ссылки",
"components.public-link-security_expiration_removal_title": "О удалении срока действия истекшей ссылки",
"components.public-link-security_expiration_removal_no_password": "Предупреждение: о удалении срока действия общедоступной ссылки, что делает её доступной для любого, кто знает её",
"components.public-link-security_expiration_removal_but_password": "Предупреждение: о удалении срока действия общедоступной ссылки, что делает её доступной для любого, кто знает пароль",
"components.public-link-security_expiration_removal_confirm": "Удалить срок действия",
"components.public-link-security_field_confirm_edit": "Готово",
"components.public-link-security_password_removal_title": "О удалении пароля",
"components.public-link-security_password_removal_body": "Предупреждение: о удалении пароля с общедоступной ссылки, что делает её доступной для любого, кто знает её",
"components.public-link-security_password_removal_confirm": "Удалить пароль",
"components.public-link-access-level-update-subtitle": "У каждого, кто имеет ссылку, есть доступ к",
"components.public-link-security": "Настройки безопасности ссылки",
"components.public-link-security-change": "Изменить",
"components.public-link-security-change-subtitle": "Установить пароль, срок действия и т.д.",
"components.public-link-security-title": "Поделиться ссылкой",
"components.internal-access_access_manage":"General access management",
"components.internal-access_inherit_parent":"Inherit parent folder",
"components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.",
+14 -1
View File
@@ -195,10 +195,23 @@
"components.public-link-copied-info": "Đã sao chép liên kết",
"components.public-link-get" : "Tạo đường link",
"components.public-link-copy": "Sao chép đường dẫn",
"components.public-link-security": "Bảo mật liên kết công khai",
"components.public-link-security_password": "Mật khẩu",
"components.public-link-security_password_copied": "Mật khẩu đã được sao chép vào khay nhớ tạm",
"components.public-link-security_expired": "Hết hạn",
"components.public-link-security_expiration_title": "Hết hạn liên kết",
"components.public-link-security_expiration_removal_title": "Về việc xóa ngày hết hạn của một liên kết đã hết hạn",
"components.public-link-security_expiration_removal_no_password": "Cảnh báo: về việc xóa ngày hết hạn của liên kết công cộng, khiến nó có sẵn cho bất kỳ ai biết nó",
"components.public-link-security_expiration_removal_but_password": "Cảnh báo: về việc xóa ngày hết hạn của liên kết công cộng, khiến nó có sẵn cho bất kỳ ai có mật khẩu",
"components.public-link-security_expiration_removal_confirm": "Xóa hết hạn",
"components.public-link-security_field_confirm_edit": "Hoàn tất",
"components.public-link-security_password_removal_title": "Về việc xóa mật khẩu",
"components.public-link-security_password_removal_body": "Cảnh báo: về việc xóa mật khẩu của liên kết công cộng, khiến nó có sẵn cho bất kỳ ai biết nó",
"components.public-link-security_password_removal_confirm": "Xóa mật khẩu",
"components.public-link-access-level-update-subtitle": "Bất kỳ ai có liên kết đều có quyền truy cập vào",
"components.public-link-security": "Cài đặt bảo mật liên kết",
"components.public-link-security-change": "Thay đổi",
"components.public-link-security-change-subtitle": "Thiết lập mật khẩu, ngày hết hạn, vv.",
"components.public-link-security-title": "Chia sẻ với liên kết",
"components.internal-access_access_manage": "Quản lý quyền truy cập chung",
"components.internal-access_inherit_parent": "Thừa hưởng từ thư mục cha",
"components.internal-access_inherit_parent_perm": "Chọn có thừa hưởng quyền của thư mục cha hay không.",
@@ -0,0 +1,189 @@
import moment from 'moment';
import 'moment/min/locales';
import { useEffect, useRef, useState } from 'react';
import { uniqueId } from 'lodash';
import Languages from 'features/global/services/languages-service';
import BaseBlock from '@molecules/grouped-rows/base';
import { Base, Info } from '@atoms/text';
import { Input } from 'app/atoms/input/input-text';
import { CheckboxSlider } from 'app/atoms/input/input-checkbox-slider';
import { Button } from 'app/atoms/button/button';
import { ShieldExclamationIcon, CalendarIcon, PencilAltIcon } from '@heroicons/react/outline';
import { Modal, ModalContent } from 'app/atoms/modal';
import Styles from './styles';
export const ExpiryEditorRow = (props: {
disabled?: boolean;
value: number;
isLinkPasswordProtected: boolean;
onChange: (value: number) => Promise<void>;
}) => {
const [isEditing, setIsEditing] = useState(false);
const [currentEditedValue, setCurrentEditedValue] = useState(0);
const [isWaitingForSave, setIsWaitingForSave] = useState(false);
const [isConfirmingExpiryRemoval, setIsConfirmingExpiryRemoval] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const [chkExpiryId] = useState(uniqueId("chk"));
const disabled = props.disabled || isWaitingForSave;
useEffect(() => {
if (!isEditing || !inputRef.current) return;
let timeout: NodeJS.Timeout | null = setTimeout(() => {
timeout = null;
inputRef.current?.focus();
}, 10);
return () => {
if (timeout) clearTimeout(timeout);
timeout = null;
}
}, [ isEditing ])
function saveValue(value: number) {
setIsWaitingForSave(true);
props.
onChange(value).
then(
() => {
setIsWaitingForSave(false);
setIsEditing(false);
},
(e: unknown) => {
console.error("Error while saving expiry date:", e);
setIsWaitingForSave(false);
},
);
}
function confirmSaveExpiry(expiry: number) {
if (expiry === props.value) {
setIsEditing(false);
return;
}
if (expiry == 0 && (props.value > 0 && props.value < Date.now()))
setIsConfirmingExpiryRemoval(true);
else
saveValue(expiry);
}
function expirationDate(exp: moment.MomentInput) {
moment.locale(Languages.getLanguage());
return moment(exp).fromNow(true).toLocaleString();
}
return <>
<BaseBlock
className={"m-4" + (disabled ? Styles.Disabled.Yes : "")}
disabled={disabled}
avatar={<CalendarIcon className={Styles.SmallIconClasses} />}
title={
<Base>
<label htmlFor={chkExpiryId}>{Languages.t('components.public-link-security_expiration_title')}</label>
{!!props.value && !isEditing &&
<>
<a
className={disabled ? Styles.Disabled.Yes : ""}
onClick={() => {
if (!disabled) {
setCurrentEditedValue(props.value);
setIsEditing(true);
}
return false;
}}
>
<PencilAltIcon className={Styles.SmallIconClasses + "ml-1 inline align-bottom"} />
</a>
{(currentEditedValue || props.value) <= Date.now()
? <Info className="ml-2 !text-red-500">
({Languages.t('components.public-link-security_expired')})
</Info>
: <Info className="ml-2">({expirationDate(currentEditedValue || props.value)})</Info>
}{' '}
</>
}
</Base>
}
subtitle={''}
suffix={isEditing
? <>
<Input
inputRef={inputRef}
disabled={disabled}
className="max-w-xs mr-4 mt-1"
type="date"
size='sm'
min={moment().add(1, 'days').toISOString().split('T')[0]}
value={new Date(currentEditedValue).toISOString().split('T')[0]}
onChange={e => e.target.value && setCurrentEditedValue(new Date(e.target.value).getTime())}
onKeyUp={e => {
if (e.key == 'Escape')
setIsEditing(false);
else if (e.key == 'Enter')
confirmSaveExpiry(currentEditedValue);
}}
/>
<Button
disabled={disabled}
theme="default"
size="sm"
className="max-w-xs mr-2 mt-1"
onClick={() => confirmSaveExpiry(currentEditedValue)}
>
{Languages.t('components.public-link-security_field_confirm_edit')}
</Button>
</>
: <CheckboxSlider
id={chkExpiryId}
checked={!!props.value}
disabled={disabled}
onChange={({target: { checked }}) => {
if (checked) {
setIsEditing(checked);
if (checked && !currentEditedValue)
setCurrentEditedValue(props.value || moment().add(7, 'days').unix() * 1000);
} else
confirmSaveExpiry(0);
}}
/>
}
/>
<Modal open={isConfirmingExpiryRemoval} onClose={() => setIsConfirmingExpiryRemoval(false)}>
<ModalContent
title={Languages.t("components.public-link-security_expiration_removal_title")}
text={props.isLinkPasswordProtected
? Languages.t("components.public-link-security_expiration_removal_but_password")
: Languages.t("components.public-link-security_expiration_removal_no_password")
}
theme="danger"
icon={ShieldExclamationIcon}
buttons={
<>
<Button
className="ml-2"
theme='default'
onClick={() => setIsConfirmingExpiryRemoval(false)}
>
{Languages.t("general.cancel")}
</Button>
<Button
theme="danger"
onClick={() => {
setIsConfirmingExpiryRemoval(false)
saveValue(0);
}}
>
{Languages.t("components.public-link-security_expiration_removal_confirm")}
</Button>
</>
}
/>
</Modal>
</>;
}
@@ -1,13 +1,21 @@
import { atom, useRecoilState } from 'recoil';
import { useState, useEffect } from 'react';
import { useDriveItem } from '@features/drive/hooks/use-drive-item';
import { useCurrentCompany } from '@features/companies/hooks/use-companies';
import Languages from 'features/global/services/languages-service';
import { useDriveItem, getPublicLink } from '@features/drive/hooks/use-drive-item';
import { useCurrentCompany } from '@features/companies/hooks/use-companies';
import { changePublicLink, hasAnyPublicLinkAccess } from '@features/files/utils/access-info-helpers';
import type { DriveFileAccessLevel } from '@features/drive/types';
import A from '@atoms/link';
import { Subtitle } from '@atoms/text';
import { LockClosedIcon, EyeIcon, PencilIcon, ScaleIcon, EyeOffIcon } from '@heroicons/react/outline';
import { Modal, ModalContent } from '@atoms/modal';
import { PublicLinkAccessOptions } from '../public-link/public-link-access-options';
import BaseBlock from '@molecules/grouped-rows/base';
import { AccessLevelDropdown, translateAccessLevel } from '../../components/access-level-dropdown';
import { CopyLinkButton } from './copy-link-button';
import Styles from './styles';
export type PublicLinkModalType = {
open: boolean;
@@ -30,6 +38,7 @@ export const PublicLinkModal = () => {
<Modal
open={state.open}
onClose={() => {
setIsOnAdvancedScreen(false);
setState({ ...state, open: false });
}}
>
@@ -43,6 +52,61 @@ export const PublicLinkModal = () => {
);
};
const ChangePublicLinkAccessLevelRow = (props: {
disabled?: boolean;
level: DriveFileAccessLevel | null;
onChange: (level: DriveFileAccessLevel & 'remove') => void;
}) => {
const IconForLevel = {
'manage': ScaleIcon,
'write': PencilIcon,
'read': EyeIcon,
'none': EyeOffIcon,
}[props.level || 'none'];
return (
<BaseBlock
className='p-4 flex flex-row items-center justify-center text-zinc-800 dark:text-white'
avatar={<IconForLevel className="w-5 mr-2" />}
title={translateAccessLevel(props.level || 'none')}
subtitle={Languages.t("components.public-link-access-level-update-subtitle")}
suffix={
<AccessLevelDropdown
className='!p-0 leading-tight text-end !pr-8 border-none bg-transparent dark:bg-transparent'
disabled={props.disabled}
size={'sm'}
level={props.level}
hiddenLevels={['remove']}
onChange={props.onChange}
/>
}
/>
);
}
const SwitchToAdvancedSettingsRow = (props: {
disabled: boolean,
onShowAdvancedScreen: (active: boolean) => void,
}) =>
<BaseBlock
className='p-4 flex flex-row items-center justify-center text-zinc-800 dark:text-white'
avatar={<LockClosedIcon className="w-5 mr-2" />}
title={Languages.t('components.public-link-security')}
suffix={
<A
className={"pr-4 inline-block" + (props.disabled ? ' !text-zinc-500' : '')}
disabled={props.disabled}
noColor={props.disabled}
onClick={() => {
if (!props.disabled)
props.onShowAdvancedScreen(true);
}}
>
{Languages.t("components.public-link-security-change")}
</A>
}
subtitle={Languages.t("components.public-link-security-change-subtitle")}
/>;
const PublicLinkModalContent = (props: {
id: string,
isOnAdvancedScreen: boolean,
@@ -57,19 +121,63 @@ const PublicLinkModalContent = (props: {
refreshCompany();
}, []);
const havePublicLink = hasAnyPublicLinkAccess(item);
const publicLink = getPublicLink(item);
return (
<ModalContent
title={
<>
{Languages.t(props.isOnAdvancedScreen
? 'components.item_context_menu.manage_access_advanced_to'
: 'components.item_context_menu.manage_access_to') + ' '}
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<Subtitle>Im content ! {havePublicLink ? "Got a" : "No"} public link.</Subtitle>
<div className="rounded-md border dark:border-zinc-700 my-5 mb-8">
<ChangePublicLinkAccessLevelRow
disabled={loading}
level={item?.access_info?.public?.level || 'none'}
onChange={level => {
item && update(changePublicLink(item, { level }));
}}
/>
<SwitchToAdvancedSettingsRow
disabled={!havePublicLink}
onShowAdvancedScreen={props.onShowAdvancedScreen}
/>
</div>
<div className="flex flex-row place-content-end my-4">
<CopyLinkButton textToCopy={havePublicLink && publicLink} />
</div>
<Modal
open={props.isOnAdvancedScreen}
onClose={() => { props.onShowAdvancedScreen(false); }}
>
<ModalContent
title={
<>
{Languages.t('components.public-link-security-title') + ' '}
<strong>{item?.name}</strong>
</>
}
>
<div className='my-4'><Subtitle>{Languages.t('components.public-link-security')}</Subtitle></div>
<div className={Styles.RoundedBorderSection}>
<PublicLinkAccessOptions
disabled={loading || access !== 'manage'}
password={item?.access_info?.public?.password}
expiration={item?.access_info?.public?.expiration}
onChangePassword={async (password: string) => {
item && await update(changePublicLink(item, { password: password || '' }));
}}
onChangeExpiration={async (expiration: number) => {
item && await update(changePublicLink(item, { expiration }));
}}
/>
</div>
</ModalContent>
</Modal>
</ModalContent>
);
}
@@ -0,0 +1,168 @@
import { useEffect, useRef, useState } from 'react';
import { uniqueId } from 'lodash';
import Languages from 'features/global/services/languages-service';
import BaseBlock from '@molecules/grouped-rows/base';
import { Base } from '@atoms/text';
import { Input } from 'app/atoms/input/input-text';
import { CheckboxSlider } from 'app/atoms/input/input-checkbox-slider';
import { Button } from 'app/atoms/button/button';
import { ShieldCheckIcon, PencilAltIcon, ShieldExclamationIcon } from '@heroicons/react/outline';
import { Modal, ModalContent } from 'app/atoms/modal';
import Styles from './styles';
export const PasswordEditorRow = (props: {
disabled?: boolean;
password?: string;
isLinkExpired: boolean;
onChangePassword: (password: string) => Promise<void>;
}) => {
const [isEditingPassword, setIsEditingPassword] = useState(false);
const [currentEditedPassword, setCurrentEditedPassword] = useState("");
const [isWaitingForPasswordSave, setIsWaitingForPasswordSave] = useState(false);
const [isConfirmingPasswordRemoval, setIsConfirmingPasswordRemoval] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const [chkPasswordId] = useState(uniqueId("chk"));
const disabled = props.disabled || isWaitingForPasswordSave;
useEffect(() => {
if (!isEditingPassword || !inputRef.current) return;
let timeout: NodeJS.Timeout | null = setTimeout(() => {
timeout = null;
inputRef.current?.focus();
}, 10);
return () => {
if (timeout) clearTimeout(timeout);
timeout = null;
}
}, [ isEditingPassword ])
function savePassword(password: string) {
setIsWaitingForPasswordSave(true);
props.
onChangePassword(password).
then(
() => {
setIsWaitingForPasswordSave(false);
setIsEditingPassword(false);
},
(e: unknown) => {
console.error("Error while saving password:", e);
setIsWaitingForPasswordSave(false);
},
);
}
function confirmSavePassword(password: string) {
if (password === (props.password ?? "")) {
setIsEditingPassword(false);
return;
}
// If link is also expired, removing the password won't expose it,
// no need to stress the user with a confirmation
if (!password.length && !props.isLinkExpired)
setIsConfirmingPasswordRemoval(true);
else
savePassword(password);
}
return <>
<BaseBlock
className={"m-4" + (disabled ? Styles.Disabled.Yes : "")}
disabled={disabled}
avatar={<ShieldCheckIcon className={Styles.SmallIconClasses} />}
title={
<Base>
<label htmlFor={chkPasswordId}>{Languages.t('components.public-link-security_password')}</label>
{!!props.password?.length && !isEditingPassword &&
<a
className={disabled ? Styles.Disabled.Yes : ""}
onClick={() => {
if (!disabled) {
setCurrentEditedPassword(props.password!);
setIsEditingPassword(true);
}
}}
>
<PencilAltIcon className={Styles.SmallIconClasses + "ml-1 inline align-bottom"} />
</a>
}
</Base>
}
subtitle={''}
suffix={isEditingPassword
? <>
<Input
inputRef={inputRef}
disabled={disabled}
className="max-w-xs mr-4 mt-1"
size='sm'
value={currentEditedPassword}
onChange={e => setCurrentEditedPassword(e.target.value)}
onKeyUp={e => {
if (e.key == 'Escape')
setIsEditingPassword(false);
else if (e.key == 'Enter')
confirmSavePassword(currentEditedPassword);
}}
/>
<Button
disabled={disabled}
theme="default"
size="sm"
className="max-w-xs mr-2 mt-1"
onClick={() => confirmSavePassword(currentEditedPassword)}
>
{Languages.t('components.public-link-security_field_confirm_edit')}
</Button>
</>
: <CheckboxSlider
id={chkPasswordId}
checked={!!props.password?.length}
disabled={disabled}
onChange={({target: { checked }}) => {
if (checked) {
setIsEditingPassword(checked);
if (checked && !currentEditedPassword.length)
setCurrentEditedPassword(!props.password?.length ? Math.random().toString(36).slice(-8) : props.password);
} else
setIsConfirmingPasswordRemoval(true);
}}
/>
}
/>
<Modal open={isConfirmingPasswordRemoval} onClose={() => setIsConfirmingPasswordRemoval(false)}>
<ModalContent
title={Languages.t("components.public-link-security_password_removal_title")}
text={Languages.t("components.public-link-security_password_removal_body")}
theme="danger"
icon={ShieldExclamationIcon}
buttons={
<>
<Button
className="ml-2"
theme='default'
onClick={() => setIsConfirmingPasswordRemoval(false)}
>
{Languages.t("general.cancel")}
</Button>
<Button
theme="danger"
onClick={() => {
setIsConfirmingPasswordRemoval(false)
savePassword("");
}}
>
{Languages.t("components.public-link-security_password_removal_confirm")}
</Button>
</>
}
/>
</Modal>
</>;
}
@@ -1,120 +1,26 @@
import { Info, Subtitle } from '@atoms/text';
import { ToasterService } from '@features/global/services/toaster-service';
import { copyToClipboard } from '@features/global/utils/CopyClipboard';
import { Checkbox } from 'app/atoms/input/input-checkbox';
import { Input } from 'app/atoms/input/input-text';
import { useEffect, useRef, useState } from 'react';
import moment from 'moment';
import 'moment/min/locales';
import Languages from 'features/global/services/languages-service';
import { debounce } from 'lodash';
import { PasswordEditorRow } from './password-editor-row';
import { ExpiryEditorRow } from './expiry-editor-row';
export const PublicLinkAccessOptions = (props: {
disabled?: boolean;
password?: string;
expiration?: number;
onChangePassword: Function;
onChangeExpiration: Function;
}) => {
const [usePassword, setUsePassword] = useState(!!props.password);
const [password, setPassword] = useState(props.password);
const [useExpiration, setUseExpiration] = useState((props.expiration || 0) > 0);
const [expiration, setExpiration] = useState(props.expiration);
const handlePasswordBlur = () => {
props.onChangePassword(password);
};
onChangePassword: (password: string) => Promise<void>;
onChangeExpiration: (expiration: number) => Promise<void>;
}) => (
<>
<PasswordEditorRow
disabled={props.disabled}
password={props.password}
onChangePassword={props.onChangePassword}
isLinkExpired={!!props.expiration && props.expiration < Date.now()}
/>
<ExpiryEditorRow
disabled={props.disabled}
value={props.expiration ?? 0}
onChange={props.onChangeExpiration}
isLinkPasswordProtected={!!props.password?.length}
/>
</>
);
const debouncedOnChangePassword = debounce(passwordValue => {
props.onChangePassword(passwordValue);
}, 500); // 500ms delay
useEffect(() => {
// Ensure the effect runs only if usePassword or password changes
debouncedOnChangePassword(usePassword ? password : '');
// Cleanup function to cancel the debounced call if the component is unmounted or the dependencies change
return () => {
debouncedOnChangePassword.cancel();
};
}, [usePassword, password]);
useEffect(() => {
props.onChangeExpiration(useExpiration ? expiration : 0);
}, [useExpiration, expiration]);
function expirationDate(exp: moment.MomentInput) {
moment.locale(Languages.getLanguage());
return moment(exp).fromNow(true).toLocaleString();
}
return (
<>
<Subtitle className="block mt-4 mb-1">
{Languages.t('components.public-link-security')}
</Subtitle>
<div className="p-4 rounded-md border">
<div className="flex items-center justify-center w-full h-10">
<Checkbox
disabled={props.disabled}
labelNormalSize={true}
onChange={s => {
setUsePassword(s);
if (!password && s) setPassword(Math.random().toString(36).slice(-8));
}}
value={!!usePassword}
label={Languages.t('components.public-link-security_password')}
/>
<div className="grow mr-2" />
{!!usePassword && (
<Input
disabled={props.disabled}
className="max-w-xs"
value={password}
onChange={e => setPassword(e.target.value)}
// saves and copies password
onClick={() => {
if (password) copyToClipboard(password);
ToasterService.success(
Languages.t('components.public-link-security_password_copied'),
);
}}
/>
)}
</div>
<div className="flex items-center justify-center w-full h-10">
<Checkbox
disabled={props.disabled}
labelNormalSize={true}
onChange={s => {
setUseExpiration(s);
if (!expiration && s) setExpiration(Date.now() + 1000 * 60 * 60 * 24 * 7);
}}
value={!!useExpiration}
label={Languages.t('components.public-link-security_expired')}
/>
{useExpiration && (expiration || 0) < Date.now() && (
<Info className="ml-2 text-red-500">
({Languages.t('components.public-link-security_expired')})
</Info>
)}
{useExpiration && (expiration || 0) > Date.now() && (
<Info className="ml-2">({expirationDate(expiration)})</Info>
)}{' '}
<div className="grow mr-2" />
{!!useExpiration && (
<Input
disabled={props.disabled}
type="date"
className="max-w-xs"
value={new Date(expiration || 0).toISOString().split('T')[0]}
onChange={e => {
e.target.value && setExpiration(new Date(e.target.value).getTime());
}}
/>
)}
</div>
</div>
</>
);
};
@@ -1,12 +1,10 @@
import { Subtitle } from '@atoms/text';
import { useDriveItem, getPublicLink } from '@features/drive/hooks/use-drive-item';
import { copyToClipboard } from '@features/global/utils/CopyClipboard';
import { Input } from 'app/atoms/input/input-text';
import { useState } from 'react';
import { AccessLevelDropdown } from '../../components/access-level-dropdown';
import Languages from 'features/global/services/languages-service';
import { Button } from '@atoms/button/button';
import { LinkIcon, UserGroupIcon, CheckCircleIcon } from '@heroicons/react/outline';
import { UserGroupIcon } from '@heroicons/react/outline';
import type { DriveFileAccessLevelForPublicLink } from 'app/features/drive/types';
import { changePublicLink } from '@features/files/utils/access-info-helpers';
import { CopyLinkButton } from './copy-link-button';
@@ -24,7 +22,6 @@ export const PublicLinkManager = ({ id, disabled }: { id: string; disabled?: boo
if (level === 'none')
setPublicLinkCreationLevel(defaultPublicLinkLevel);
};
const [didJustCompleteACopy, setDidJustCompleteACopy] = useState<boolean>(false);
return (
<>
@@ -57,7 +54,7 @@ export const PublicLinkManager = ({ id, disabled }: { id: string; disabled?: boo
</div>
<div className="shrink-0">
<AccessLevelDropdown
hiddenLevels={['manage'].concat(havePublicLink ? [] : ['none'])}
hiddenLevels={['manage', 'remove'].concat(havePublicLink ? [] : ['none'])}
disabled={loading || disabled}
level={publicLinkCreationLevelSafe}
labelOverrides={{'none': Languages.t('components.public-link-access-level-delete')}}
@@ -0,0 +1,7 @@
export default {
RoundedBorderSection: "rounded-md border dark:border-zinc-700 my-5 mb-8",
SmallIconClasses: " w-5 mr-2 dark:text-white ",
Disabled: {
Yes: " opacity-50 cursor-default ",
},
}
@@ -109,43 +109,11 @@ const AccessModalContent = (props: {
</>
}
>
{!props.isOnAdvancedScreen ?
<>
<PublicLinkManager id={id} disabled={access !== 'manage'} />
<PublicLinkManager id={id} disabled={access !== 'manage'} />
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && (
<InternalAccessManager id={id} disabled={access !== 'manage'} />
)}
{haveAdvancedSettings && <SwitchToAdvancedSettingsPanel
disabled={!haveAdvancedSettings}
onShowAdvancedScreen={props.onShowAdvancedScreen}
/>}
</> : <>
{havePublicLink &&
<PublicLinkAccessOptions
disabled={loading || access !== 'manage'}
password={item?.access_info?.public?.password}
expiration={item?.access_info?.public?.expiration}
onChangePassword={(password: string) => {
item && changePublicLink(item, { password: password || '' });
}}
onChangeExpiration={(expiration: number) => {
item && changePublicLink(item, { expiration: expiration || 0 });
}}
/>}
{ parentItem?.parent_id !== null && <>
<InheritAccessOptions
item={item}
disabled={loading}
onUpdate={update}
/>
<CuteDepictionOfFolderHierarchy
file={item}
parent={parentItem}
/>
</>}
</>}
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && (
<InternalAccessManager id={id} disabled={access !== 'manage'} />
)}
</ModalContent>
);
};