2022-07-18 10:47:54 +02:00
|
|
|
|
import * as React from 'react';
|
2025-01-22 09:55:36 +01:00
|
|
|
|
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
|
2022-07-18 10:47:54 +02:00
|
|
|
|
import I18n from 'i18n-js';
|
|
|
|
|
|
|
|
|
|
|
|
import Box from '../../common/Box';
|
|
|
|
|
|
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
|
|
|
|
|
import Button from '../../common/Button';
|
|
|
|
|
|
import HttpStatus from '../../../constants/http_status';
|
|
|
|
|
|
import {
|
2023-02-04 15:43:15 +01:00
|
|
|
|
TENANT_SETTING_BRAND_DISPLAY_NAME_AND_LOGO,
|
|
|
|
|
|
TENANT_SETTING_BRAND_DISPLAY_NAME_ONLY,
|
|
|
|
|
|
TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY,
|
|
|
|
|
|
TENANT_SETTING_BRAND_DISPLAY_NONE,
|
2023-02-11 11:35:27 +01:00
|
|
|
|
TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_NO_COLLAPSE,
|
|
|
|
|
|
TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_ALWAYS_COLLAPSE,
|
2024-09-16 18:48:18 +02:00
|
|
|
|
TENANT_SETTING_LOGO_LINKS_TO_ROOT_PAGE,
|
|
|
|
|
|
TENANT_SETTING_LOGO_LINKS_TO_CUSTOM_URL,
|
|
|
|
|
|
TENANT_SETTING_LOGO_LINKS_TO_NOTHING,
|
2023-02-04 15:43:15 +01:00
|
|
|
|
} from '../../../interfaces/ITenantSetting';
|
2023-02-05 11:55:38 +01:00
|
|
|
|
import { DangerText, SmallMutedText } from '../../common/CustomTexts';
|
2022-08-05 18:15:17 +02:00
|
|
|
|
import { getLabel, getValidationMessage } from '../../../helpers/formUtils';
|
2023-02-05 11:55:38 +01:00
|
|
|
|
import IBoardJSON from '../../../interfaces/json/IBoard';
|
2024-03-24 12:54:02 +01:00
|
|
|
|
import ActionLink from '../../common/ActionLink';
|
2025-01-22 11:52:01 +01:00
|
|
|
|
import { CancelIcon, DeleteIcon, EditIcon, LearnMoreIcon } from '../../common/Icons';
|
2025-01-22 09:55:36 +01:00
|
|
|
|
import Dropzone from '../../common/Dropzone';
|
2022-07-22 16:50:36 +02:00
|
|
|
|
|
|
|
|
|
|
export interface ISiteSettingsGeneralForm {
|
|
|
|
|
|
siteName: string;
|
2025-01-27 12:42:23 +01:00
|
|
|
|
siteLogo?: File;
|
|
|
|
|
|
shouldDeleteSiteLogo: boolean;
|
2025-01-22 09:55:36 +01:00
|
|
|
|
oldSiteLogo: string;
|
2025-01-27 12:42:23 +01:00
|
|
|
|
siteFavicon?: File;
|
|
|
|
|
|
shouldDeleteSiteFavicon: boolean;
|
2022-07-22 16:50:36 +02:00
|
|
|
|
brandDisplaySetting: string;
|
|
|
|
|
|
locale: string;
|
2024-09-08 14:40:48 +02:00
|
|
|
|
useBrowserLocale: boolean;
|
2023-02-05 11:55:38 +01:00
|
|
|
|
rootBoardId?: string;
|
2024-03-24 12:54:02 +01:00
|
|
|
|
customDomain?: string;
|
2024-08-29 22:14:04 +02:00
|
|
|
|
isPrivate: boolean;
|
2024-07-12 20:38:46 +02:00
|
|
|
|
allowAnonymousFeedback: boolean;
|
|
|
|
|
|
feedbackApprovalPolicy: string;
|
2025-01-27 12:57:44 +01:00
|
|
|
|
allowAttachmentUpload: boolean;
|
2024-09-16 18:48:18 +02:00
|
|
|
|
logoLinksTo: string;
|
|
|
|
|
|
logoCustomUrl?: string;
|
2023-02-11 11:35:27 +01:00
|
|
|
|
showRoadmapInHeader: boolean;
|
|
|
|
|
|
collapseBoardsInHeader: string;
|
2024-07-12 20:38:46 +02:00
|
|
|
|
showVoteCount: boolean;
|
|
|
|
|
|
showVoteButtonInBoard: boolean;
|
2024-09-17 18:36:54 +02:00
|
|
|
|
hideUnusedStatusesInFilterByStatus: boolean;
|
2024-07-12 20:38:46 +02:00
|
|
|
|
showPoweredBy: boolean;
|
2022-07-22 16:50:36 +02:00
|
|
|
|
}
|
2022-07-18 10:47:54 +02:00
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
originForm: ISiteSettingsGeneralForm;
|
2025-01-22 09:55:36 +01:00
|
|
|
|
siteLogoUrl?: string;
|
2025-01-22 14:04:08 +01:00
|
|
|
|
siteFaviconUrl?: string;
|
2023-02-05 11:55:38 +01:00
|
|
|
|
boards: IBoardJSON[];
|
2024-03-28 12:29:54 +01:00
|
|
|
|
isMultiTenant: boolean;
|
2022-07-18 10:47:54 +02:00
|
|
|
|
authenticityToken: string;
|
|
|
|
|
|
|
|
|
|
|
|
areUpdating: boolean;
|
|
|
|
|
|
error: string;
|
|
|
|
|
|
|
|
|
|
|
|
updateTenant(
|
|
|
|
|
|
siteName: string,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
siteLogo: File,
|
2025-01-22 11:52:01 +01:00
|
|
|
|
shouldDeleteSiteLogo: boolean,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
oldSiteLogo: string,
|
2025-01-22 14:04:08 +01:00
|
|
|
|
siteFavicon: File,
|
|
|
|
|
|
shouldDeleteSiteFavicon: boolean,
|
2022-07-18 10:47:54 +02:00
|
|
|
|
brandDisplaySetting: string,
|
|
|
|
|
|
locale: string,
|
2024-09-08 14:40:48 +02:00
|
|
|
|
useBrowserLocale: boolean,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
rootBoardId: number,
|
2024-03-24 12:54:02 +01:00
|
|
|
|
customDomain: string,
|
2024-08-29 22:14:04 +02:00
|
|
|
|
isPrivate: boolean,
|
2024-07-12 20:38:46 +02:00
|
|
|
|
allowAnonymousFeedback: boolean,
|
|
|
|
|
|
feedbackApprovalPolicy: string,
|
2025-01-27 12:57:44 +01:00
|
|
|
|
allowAttachmentUpload: boolean,
|
2024-09-16 18:48:18 +02:00
|
|
|
|
logoLinksTo: string,
|
|
|
|
|
|
logoCustomUrl: string,
|
2023-02-11 11:35:27 +01:00
|
|
|
|
showRoadmapInHeader: boolean,
|
|
|
|
|
|
collapseBoardsInHeader: string,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
showVoteCount: boolean,
|
|
|
|
|
|
showVoteButtonInBoard: boolean,
|
2024-09-17 18:36:54 +02:00
|
|
|
|
hideUnusedStatusesInFilterByStatus: boolean,
|
2024-02-27 18:32:14 +01:00
|
|
|
|
showPoweredBy: boolean,
|
2022-07-18 10:47:54 +02:00
|
|
|
|
authenticityToken: string
|
|
|
|
|
|
): Promise<any>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-22 16:50:36 +02:00
|
|
|
|
const GeneralSiteSettingsP = ({
|
|
|
|
|
|
originForm,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
siteLogoUrl,
|
2025-01-22 14:04:08 +01:00
|
|
|
|
siteFaviconUrl,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
boards,
|
2024-03-28 12:29:54 +01:00
|
|
|
|
isMultiTenant,
|
2022-07-22 16:50:36 +02:00
|
|
|
|
authenticityToken,
|
|
|
|
|
|
|
|
|
|
|
|
areUpdating,
|
|
|
|
|
|
error,
|
|
|
|
|
|
updateTenant,
|
|
|
|
|
|
}: Props) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
register,
|
|
|
|
|
|
handleSubmit,
|
2024-03-24 12:54:02 +01:00
|
|
|
|
formState: { isDirty, isSubmitSuccessful, errors },
|
|
|
|
|
|
watch,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
control,
|
2022-07-22 16:50:36 +02:00
|
|
|
|
} = useForm<ISiteSettingsGeneralForm>({
|
|
|
|
|
|
defaultValues: {
|
|
|
|
|
|
siteName: originForm.siteName,
|
2025-01-27 12:42:23 +01:00
|
|
|
|
siteLogo: null,
|
|
|
|
|
|
shouldDeleteSiteLogo: false,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
oldSiteLogo: originForm.oldSiteLogo,
|
2025-01-27 12:42:23 +01:00
|
|
|
|
siteFavicon: null,
|
|
|
|
|
|
shouldDeleteSiteFavicon: false,
|
2022-07-22 16:50:36 +02:00
|
|
|
|
brandDisplaySetting: originForm.brandDisplaySetting,
|
|
|
|
|
|
locale: originForm.locale,
|
2024-09-08 14:40:48 +02:00
|
|
|
|
useBrowserLocale: originForm.useBrowserLocale,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
rootBoardId: originForm.rootBoardId,
|
2024-03-24 12:54:02 +01:00
|
|
|
|
customDomain: originForm.customDomain,
|
2024-08-29 22:14:04 +02:00
|
|
|
|
isPrivate: originForm.isPrivate,
|
2024-07-12 20:38:46 +02:00
|
|
|
|
allowAnonymousFeedback: originForm.allowAnonymousFeedback,
|
|
|
|
|
|
feedbackApprovalPolicy: originForm.feedbackApprovalPolicy,
|
2025-01-27 12:57:44 +01:00
|
|
|
|
allowAttachmentUpload: originForm.allowAttachmentUpload,
|
2024-09-16 18:48:18 +02:00
|
|
|
|
logoLinksTo: originForm.logoLinksTo,
|
|
|
|
|
|
logoCustomUrl: originForm.logoCustomUrl,
|
2023-02-11 11:35:27 +01:00
|
|
|
|
showRoadmapInHeader: originForm.showRoadmapInHeader,
|
|
|
|
|
|
collapseBoardsInHeader: originForm.collapseBoardsInHeader,
|
2024-07-12 20:38:46 +02:00
|
|
|
|
showVoteCount: originForm.showVoteCount,
|
|
|
|
|
|
showVoteButtonInBoard: originForm.showVoteButtonInBoard,
|
2024-09-17 18:36:54 +02:00
|
|
|
|
hideUnusedStatusesInFilterByStatus: originForm.hideUnusedStatusesInFilterByStatus,
|
2024-07-12 20:38:46 +02:00
|
|
|
|
showPoweredBy: originForm.showPoweredBy,
|
2022-07-22 16:50:36 +02:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const onSubmit: SubmitHandler<ISiteSettingsGeneralForm> = data => {
|
|
|
|
|
|
updateTenant(
|
|
|
|
|
|
data.siteName,
|
2025-01-27 12:42:23 +01:00
|
|
|
|
data.siteLogo ? data.siteLogo : null,
|
|
|
|
|
|
data.shouldDeleteSiteLogo,
|
2025-01-22 09:55:36 +01:00
|
|
|
|
data.oldSiteLogo,
|
2025-01-27 12:42:23 +01:00
|
|
|
|
data.siteFavicon ? data.siteFavicon : null,
|
|
|
|
|
|
data.shouldDeleteSiteFavicon,
|
2022-07-22 16:50:36 +02:00
|
|
|
|
data.brandDisplaySetting,
|
|
|
|
|
|
data.locale,
|
2024-09-08 14:40:48 +02:00
|
|
|
|
data.useBrowserLocale,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
Number(data.rootBoardId),
|
2024-03-24 12:54:02 +01:00
|
|
|
|
data.customDomain,
|
2024-08-29 22:14:04 +02:00
|
|
|
|
data.isPrivate,
|
2024-07-12 20:38:46 +02:00
|
|
|
|
data.allowAnonymousFeedback,
|
|
|
|
|
|
data.feedbackApprovalPolicy,
|
2025-01-27 12:57:44 +01:00
|
|
|
|
data.allowAttachmentUpload,
|
2024-09-16 18:48:18 +02:00
|
|
|
|
data.logoLinksTo,
|
|
|
|
|
|
data.logoCustomUrl,
|
2023-02-11 11:35:27 +01:00
|
|
|
|
data.showRoadmapInHeader,
|
|
|
|
|
|
data.collapseBoardsInHeader,
|
2023-02-05 11:55:38 +01:00
|
|
|
|
data.showVoteCount,
|
|
|
|
|
|
data.showVoteButtonInBoard,
|
2024-09-17 18:36:54 +02:00
|
|
|
|
data.hideUnusedStatusesInFilterByStatus,
|
2024-02-27 18:32:14 +01:00
|
|
|
|
data.showPoweredBy,
|
2023-02-11 11:35:27 +01:00
|
|
|
|
authenticityToken
|
2022-07-18 10:47:54 +02:00
|
|
|
|
).then(res => {
|
|
|
|
|
|
if (res?.status !== HttpStatus.OK) return;
|
2024-07-12 20:38:46 +02:00
|
|
|
|
|
|
|
|
|
|
const urlWithoutHash = window.location.href.split('#')[0];
|
|
|
|
|
|
window.history.pushState({}, document.title, urlWithoutHash);
|
2022-07-18 10:47:54 +02:00
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
2022-07-22 16:50:36 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
if (window.location.hash) {
|
|
|
|
|
|
const anchor = window.location.hash.substring(1);
|
|
|
|
|
|
const anchorElement = document.getElementById(anchor);
|
|
|
|
|
|
|
|
|
|
|
|
if (anchorElement) {
|
|
|
|
|
|
anchorElement.classList.add('highlighted');
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout( () => {
|
|
|
|
|
|
anchorElement.scrollIntoView({
|
|
|
|
|
|
behavior: 'smooth'
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-01-27 12:42:23 +01:00
|
|
|
|
const customDomain = watch('customDomain');
|
|
|
|
|
|
const shouldDeleteSiteLogo = watch('shouldDeleteSiteLogo');
|
|
|
|
|
|
const shouldDeleteSiteFavicon = watch('shouldDeleteSiteFavicon');
|
2025-01-22 09:55:36 +01:00
|
|
|
|
|
2025-01-27 12:42:23 +01:00
|
|
|
|
const [showSiteLogoDropzone, setShowSiteLogoDropzone] = React.useState([null, undefined, ''].includes(siteLogoUrl));
|
2025-01-22 14:04:08 +01:00
|
|
|
|
const [showSiteFaviconDropzone, setShowSiteFaviconDropzone] = React.useState([null, undefined, ''].includes(siteFaviconUrl));
|
|
|
|
|
|
|
2022-07-22 16:50:36 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<>
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<Box customClass="generalSiteSettingsContainer">
|
2022-07-22 16:50:36 +02:00
|
|
|
|
<h2>{ I18n.t('site_settings.general.title') }</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
|
|
|
|
<div className="formRow">
|
2025-01-22 09:55:36 +01:00
|
|
|
|
<div className="formGroup col-6">
|
2022-08-05 18:15:17 +02:00
|
|
|
|
<label htmlFor="siteName">{ getLabel('tenant', 'site_name') }</label>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
<input
|
|
|
|
|
|
{...register('siteName', { required: true })}
|
|
|
|
|
|
id="siteName"
|
|
|
|
|
|
className="formControl"
|
|
|
|
|
|
/>
|
2022-08-05 18:15:17 +02:00
|
|
|
|
<DangerText>{errors.siteName && getValidationMessage(errors.siteName.type, 'tenant', 'site_name')}</DangerText>
|
2022-07-18 10:47:54 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-01-22 09:55:36 +01:00
|
|
|
|
<div className="formGroup col-6">
|
2023-02-05 11:55:38 +01:00
|
|
|
|
<label htmlFor="brandSetting">{ getLabel('tenant_setting', 'brand_display') }</label>
|
2022-07-18 10:47:54 +02:00
|
|
|
|
<select
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{...register('brandDisplaySetting')}
|
|
|
|
|
|
id="brandSetting"
|
2022-07-18 10:47:54 +02:00
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
2023-02-04 15:43:15 +01:00
|
|
|
|
<option value={TENANT_SETTING_BRAND_DISPLAY_NAME_AND_LOGO}>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{ I18n.t('site_settings.general.brand_setting_both') }
|
|
|
|
|
|
</option>
|
2023-02-04 15:43:15 +01:00
|
|
|
|
<option value={TENANT_SETTING_BRAND_DISPLAY_NAME_ONLY}>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{ I18n.t('site_settings.general.brand_setting_name') }
|
|
|
|
|
|
</option>
|
2023-02-04 15:43:15 +01:00
|
|
|
|
<option value={TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY}>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{ I18n.t('site_settings.general.brand_setting_logo') }
|
|
|
|
|
|
</option>
|
2023-02-04 15:43:15 +01:00
|
|
|
|
<option value={TENANT_SETTING_BRAND_DISPLAY_NONE}>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{ I18n.t('site_settings.general.brand_setting_none') }
|
|
|
|
|
|
</option>
|
2022-07-18 10:47:54 +02:00
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2025-01-22 09:55:36 +01:00
|
|
|
|
|
|
|
|
|
|
{/* Hidden oldSiteLogo field for backwards compatibility */}
|
|
|
|
|
|
<div className="formGroup d-none">
|
|
|
|
|
|
<label htmlFor="oldSiteLogo">{ getLabel('tenant', 'site_logo') }</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
{...register('oldSiteLogo')}
|
|
|
|
|
|
placeholder='https://example.com/logo.png'
|
|
|
|
|
|
id="oldSiteLogo"
|
|
|
|
|
|
className="formControl"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formRow">
|
|
|
|
|
|
<div className="formGroup col-6">
|
|
|
|
|
|
<label htmlFor="siteLogo">{ getLabel('tenant', 'site_logo') }</label>
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
siteLogoUrl &&
|
2025-01-22 11:52:01 +01:00
|
|
|
|
<div className={`siteLogoPreview${shouldDeleteSiteLogo ? ' siteLogoPreviewShouldDelete' : ''}`}>
|
|
|
|
|
|
<img src={siteLogoUrl} alt={`${originForm.siteName} logo`} className="siteLogoPreviewImg" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="siteLogoActions">
|
|
|
|
|
|
{
|
|
|
|
|
|
(siteLogoUrl && !shouldDeleteSiteLogo) &&
|
2025-01-22 09:55:36 +01:00
|
|
|
|
(showSiteLogoDropzone ?
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => setShowSiteLogoDropzone(false)}
|
|
|
|
|
|
icon={<CancelIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.cancel')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
:
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => setShowSiteLogoDropzone(true)}
|
|
|
|
|
|
icon={<EditIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.edit')}
|
|
|
|
|
|
</ActionLink>)
|
|
|
|
|
|
}
|
2025-01-22 11:52:01 +01:00
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
(siteLogoUrl && !showSiteLogoDropzone) &&
|
|
|
|
|
|
(shouldDeleteSiteLogo ?
|
2025-01-27 12:42:23 +01:00
|
|
|
|
<Controller
|
|
|
|
|
|
name="shouldDeleteSiteLogo"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => field.onChange(false)}
|
|
|
|
|
|
icon={<CancelIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.cancel')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
2025-01-22 11:52:01 +01:00
|
|
|
|
:
|
2025-01-27 12:42:23 +01:00
|
|
|
|
<Controller
|
|
|
|
|
|
name="shouldDeleteSiteLogo"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => field.onChange(true)}
|
|
|
|
|
|
icon={<DeleteIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.delete')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
2025-01-22 11:52:01 +01:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
</div>
|
2025-01-22 09:55:36 +01:00
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
showSiteLogoDropzone &&
|
|
|
|
|
|
<Controller
|
|
|
|
|
|
name="siteLogo"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<Dropzone
|
2025-01-27 12:42:23 +01:00
|
|
|
|
files={field.value ? [field.value] : []}
|
|
|
|
|
|
setFiles={files => files.length > 0 ? field.onChange(files[0]) : field.onChange(null)}
|
2025-01-22 14:04:08 +01:00
|
|
|
|
maxSizeKB={256}
|
2025-01-22 09:55:36 +01:00
|
|
|
|
maxFiles={1}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formGroup col-6">
|
2025-01-22 14:04:08 +01:00
|
|
|
|
<label htmlFor="siteFavicon">{ getLabel('tenant', 'site_favicon') }</label>
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
siteFaviconUrl &&
|
|
|
|
|
|
<div className={`siteFaviconPreview${shouldDeleteSiteFavicon ? ' siteFaviconPreviewShouldDelete' : ''}`}>
|
|
|
|
|
|
<img src={siteFaviconUrl} alt={`${originForm.siteName} favicon`} className="siteFaviconPreviewImg" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="siteFaviconActions">
|
|
|
|
|
|
{
|
|
|
|
|
|
(siteFaviconUrl && !shouldDeleteSiteFavicon) &&
|
|
|
|
|
|
(showSiteFaviconDropzone ?
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => setShowSiteFaviconDropzone(false)}
|
|
|
|
|
|
icon={<CancelIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.cancel')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
:
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => setShowSiteFaviconDropzone(true)}
|
|
|
|
|
|
icon={<EditIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.edit')}
|
|
|
|
|
|
</ActionLink>)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
(siteFaviconUrl && !showSiteFaviconDropzone) &&
|
|
|
|
|
|
(shouldDeleteSiteFavicon ?
|
2025-01-27 12:42:23 +01:00
|
|
|
|
<Controller
|
|
|
|
|
|
name="shouldDeleteSiteFavicon"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => field.onChange(false)}
|
|
|
|
|
|
icon={<CancelIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.cancel')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
2025-01-22 14:04:08 +01:00
|
|
|
|
:
|
2025-01-27 12:42:23 +01:00
|
|
|
|
<Controller
|
|
|
|
|
|
name="shouldDeleteSiteFavicon"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => field.onChange(true)}
|
|
|
|
|
|
icon={<DeleteIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('common.buttons.delete')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
2025-01-22 14:04:08 +01:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
showSiteFaviconDropzone &&
|
|
|
|
|
|
<Controller
|
|
|
|
|
|
name="siteFavicon"
|
|
|
|
|
|
control={control}
|
|
|
|
|
|
render={({ field }) => (
|
|
|
|
|
|
<Dropzone
|
2025-01-27 12:42:23 +01:00
|
|
|
|
files={field.value ? [field.value] : []}
|
|
|
|
|
|
setFiles={files => files.length > 0 ? field.onChange(files[0]) : field.onChange(null)}
|
2025-01-22 14:04:08 +01:00
|
|
|
|
maxSizeKB={64}
|
|
|
|
|
|
maxFiles={1}
|
|
|
|
|
|
accept={['image/x-icon', 'image/icon', 'image/png', 'image/jpeg', 'image/jpg']}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
2025-01-22 09:55:36 +01:00
|
|
|
|
</div>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
2022-08-05 18:15:17 +02:00
|
|
|
|
<label htmlFor="locale">{ getLabel('tenant', 'locale') }</label>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
<select
|
|
|
|
|
|
{...register('locale')}
|
|
|
|
|
|
id="locale"
|
|
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="en">🇬🇧 English</option>
|
|
|
|
|
|
<option value="it">🇮🇹 Italiano</option>
|
2022-07-23 13:58:14 +02:00
|
|
|
|
<option value="de">🇩🇪 Deutsch</option>
|
2022-07-25 15:47:10 +02:00
|
|
|
|
<option value="fr">🇫🇷 Français</option>
|
2024-01-06 17:13:56 +01:00
|
|
|
|
<option value="es">🇪🇸 Español</option>
|
2024-05-13 16:46:09 +02:00
|
|
|
|
<option value="pt">🇵🇹 Português</option>
|
2024-03-15 00:14:31 +01:00
|
|
|
|
<option value="zh-CN">🇨🇳 中文</option>
|
2022-09-22 14:20:11 +02:00
|
|
|
|
<option value="ru">🇷🇺 Русский</option>
|
2024-01-08 23:13:39 +07:00
|
|
|
|
<option value="vi">🇻🇳 Tiếng Việt</option>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
2022-07-18 10:47:54 +02:00
|
|
|
|
|
2024-09-08 14:40:48 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('useBrowserLocale')} type="checkbox" id="use_browser_locale_checkbox" />
|
|
|
|
|
|
<label htmlFor="use_browser_locale_checkbox">{ getLabel('tenant_setting', 'use_browser_locale') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.use_browser_locale_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2023-02-05 11:55:38 +01:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="rootBoardId">{ getLabel('tenant_setting', 'root_board_id') }</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
{...register('rootBoardId')}
|
|
|
|
|
|
id="rootBoardId"
|
|
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="0">
|
|
|
|
|
|
{I18n.t('roadmap.title')}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<optgroup label={getLabel('board')}>
|
|
|
|
|
|
{boards.map((board, i) => (
|
|
|
|
|
|
<option value={board.id} key={i}>{board.name}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</optgroup>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-03-28 12:29:54 +01:00
|
|
|
|
{ isMultiTenant &&
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="customDomain">{ getLabel('tenant', 'custom_domain') }</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
{...register('customDomain')}
|
|
|
|
|
|
id="customDomain"
|
|
|
|
|
|
className="formControl"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{
|
|
|
|
|
|
originForm.customDomain !== customDomain && customDomain !== '' &&
|
|
|
|
|
|
<div style={{marginTop: 16}}>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.custom_domain_help', { domain: customDomain }) }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
}
|
|
|
|
|
|
<div style={{marginTop: 8}}>
|
2024-03-24 12:54:02 +01:00
|
|
|
|
<ActionLink
|
|
|
|
|
|
onClick={() => window.open('https://docs.astuto.io/custom-domain', '_blank')}
|
|
|
|
|
|
icon={<LearnMoreIcon />}
|
|
|
|
|
|
>
|
|
|
|
|
|
{I18n.t('site_settings.general.custom_domain_learn_more')}
|
|
|
|
|
|
</ActionLink>
|
|
|
|
|
|
</div>
|
2024-03-28 12:29:54 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
}
|
2024-03-24 12:54:02 +01:00
|
|
|
|
|
2024-08-29 22:14:04 +02:00
|
|
|
|
<div id="privacy" className="settingsGroup">
|
|
|
|
|
|
<h4>{ I18n.t('site_settings.general.subtitle_privacy') }</h4>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('isPrivate')} type="checkbox" id="is_private_checkbox" />
|
|
|
|
|
|
<label htmlFor="is_private_checkbox">{ getLabel('tenant_setting', 'is_private') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.is_private_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div id="moderation" className="settingsGroup">
|
|
|
|
|
|
<h4>{ I18n.t('site_settings.general.subtitle_moderation') }</h4>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('allowAnonymousFeedback')} type="checkbox" id="allow_anonymous_feedback" />
|
|
|
|
|
|
<label htmlFor="allow_anonymous_feedback">{ getLabel('tenant_setting', 'allow_anonymous_feedback') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.allow_anonymous_feedback_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
2023-02-11 11:35:27 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="feedbackApprovalPolicy">{ getLabel('tenant_setting', 'feedback_approval_policy') }</label>
|
2024-09-16 18:48:18 +02:00
|
|
|
|
<select
|
|
|
|
|
|
{...register('feedbackApprovalPolicy')}
|
|
|
|
|
|
id="feedbackApprovalPolicy"
|
|
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="anonymous_require_approval">
|
|
|
|
|
|
{ I18n.t('site_settings.general.feedback_approval_policy_anonymous_require_approval') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value="never_require_approval">
|
|
|
|
|
|
{ I18n.t('site_settings.general.feedback_approval_policy_never_require_approval') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value="always_require_approval">
|
|
|
|
|
|
{ I18n.t('site_settings.general.feedback_approval_policy_always_require_approval') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.feedback_approval_policy_help') }
|
|
|
|
|
|
</SmallMutedText>
|
2024-07-12 20:38:46 +02:00
|
|
|
|
</div>
|
2025-01-27 12:57:44 +01:00
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('allowAttachmentUpload')} type="checkbox" id="allow_attachment_upload" />
|
|
|
|
|
|
<label htmlFor="allow_attachment_upload">{ getLabel('tenant_setting', 'allow_attachment_upload') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.allow_attachment_upload_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-07-12 20:38:46 +02:00
|
|
|
|
</div>
|
2023-02-05 11:55:38 +01:00
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div id="header" className="settingsGroup">
|
|
|
|
|
|
<h4>{ I18n.t('site_settings.general.subtitle_header') }</h4>
|
2024-09-16 18:48:18 +02:00
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="logoLinksTo">{ getLabel('tenant_setting', 'logo_links_to') }</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
{...register('logoLinksTo')}
|
|
|
|
|
|
id="logoLinksTo"
|
|
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value={TENANT_SETTING_LOGO_LINKS_TO_ROOT_PAGE}>
|
2024-09-17 18:36:54 +02:00
|
|
|
|
{ I18n.t('site_settings.general.logo_links_to_root_page') } ({watch('rootBoardId') === '0' ? I18n.t('roadmap.title') : boards.find(board => board.id === Number(watch('rootBoardId')))?.name})
|
2024-09-16 18:48:18 +02:00
|
|
|
|
</option>
|
|
|
|
|
|
<option value={TENANT_SETTING_LOGO_LINKS_TO_CUSTOM_URL}>
|
|
|
|
|
|
{ I18n.t('site_settings.general.logo_links_to_custom_url') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value={TENANT_SETTING_LOGO_LINKS_TO_NOTHING}>
|
|
|
|
|
|
{ I18n.t('site_settings.general.logo_links_to_nothing') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{ watch('logoLinksTo') === TENANT_SETTING_LOGO_LINKS_TO_CUSTOM_URL &&
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="logoCustomUrl">{ getLabel('tenant_setting', 'logo_custom_url') }</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
{...register('logoCustomUrl')}
|
|
|
|
|
|
id="logoCustomUrl"
|
|
|
|
|
|
className="formControl"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
}
|
2024-07-12 20:38:46 +02:00
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('showRoadmapInHeader')} type="checkbox" id="show_roadmap_in_header" />
|
|
|
|
|
|
<label htmlFor="show_roadmap_in_header">{ getLabel('tenant_setting', 'show_roadmap_in_header') }</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<label htmlFor="collapseBoardsInHeader">{ getLabel('tenant_setting', 'collapse_boards_in_header') }</label>
|
2024-09-16 18:48:18 +02:00
|
|
|
|
<select
|
|
|
|
|
|
{...register('collapseBoardsInHeader')}
|
|
|
|
|
|
id="collapseBoardsInHeader"
|
|
|
|
|
|
className="selectPicker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value={TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_NO_COLLAPSE}>
|
|
|
|
|
|
{ I18n.t('site_settings.general.collapse_boards_in_header_no_collapse') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value={TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_ALWAYS_COLLAPSE}>
|
|
|
|
|
|
{ I18n.t('site_settings.general.collapse_boards_in_header_always_collapse') }
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
2023-02-05 11:55:38 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div id="visibility" className="settingsGroup">
|
|
|
|
|
|
<br />
|
|
|
|
|
|
<h4>{ I18n.t('site_settings.general.subtitle_visibility') }</h4>
|
2023-02-05 11:55:38 +01:00
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('showVoteCount')} type="checkbox" id="show_vote_count_checkbox" />
|
|
|
|
|
|
<label htmlFor="show_vote_count_checkbox">{ getLabel('tenant_setting', 'show_vote_count') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.show_vote_count_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
2023-02-05 11:55:38 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('showVoteButtonInBoard')} type="checkbox" id="show_vote_button_in_board_checkbox" />
|
|
|
|
|
|
<label htmlFor="show_vote_button_in_board_checkbox">{ getLabel('tenant_setting', 'show_vote_button_in_board') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.show_vote_button_in_board_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2022-07-18 10:47:54 +02:00
|
|
|
|
|
2024-09-17 18:36:54 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('hideUnusedStatusesInFilterByStatus')} type="checkbox" id="hide_unused_statuses_in_filter_by_status_checkbox" />
|
|
|
|
|
|
<label htmlFor="hide_unused_statuses_in_filter_by_status_checkbox">{ getLabel('tenant_setting', 'hide_unused_statuses_in_filter_by_status') }</label>
|
|
|
|
|
|
<SmallMutedText>
|
|
|
|
|
|
{ I18n.t('site_settings.general.hide_unused_statuses_in_filter_by_status_help') }
|
|
|
|
|
|
</SmallMutedText>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2024-07-12 20:38:46 +02:00
|
|
|
|
<div className="formGroup">
|
|
|
|
|
|
<div className="checkboxSwitch">
|
|
|
|
|
|
<input {...register('showPoweredBy')} type="checkbox" id="show_powered_by_checkbox" />
|
|
|
|
|
|
<label htmlFor="show_powered_by_checkbox">{ getLabel('tenant_setting', 'show_powered_by') }</label>
|
|
|
|
|
|
</div>
|
2024-02-27 18:32:14 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<br />
|
|
|
|
|
|
|
2024-09-17 18:36:54 +02:00
|
|
|
|
<Button onClick={() => null} disabled={!isDirty} className="generalSiteSettingsSubmit">
|
2022-07-22 16:50:36 +02:00
|
|
|
|
{I18n.t('common.buttons.update')}
|
2022-07-18 10:47:54 +02:00
|
|
|
|
</Button>
|
2022-07-22 16:50:36 +02:00
|
|
|
|
</form>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
<SiteSettingsInfoBox
|
|
|
|
|
|
areUpdating={areUpdating}
|
|
|
|
|
|
error={error}
|
|
|
|
|
|
areDirty={isDirty && !isSubmitSuccessful}
|
2024-08-29 22:14:04 +02:00
|
|
|
|
isSticky={isDirty && !isSubmitSuccessful}
|
|
|
|
|
|
saveButton={
|
|
|
|
|
|
<Button onClick={handleSubmit(onSubmit)} disabled={!isDirty}>
|
|
|
|
|
|
{I18n.t('common.buttons.update')}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
}
|
2022-07-22 16:50:36 +02:00
|
|
|
|
/>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
2022-07-18 10:47:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-17 18:36:54 +02:00
|
|
|
|
export default GeneralSiteSettingsP;
|