import * as React from 'react'; import { useForm, SubmitHandler, Controller } from 'react-hook-form'; 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 { TENANT_SETTING_BRAND_DISPLAY_NAME_AND_LOGO, TENANT_SETTING_BRAND_DISPLAY_NAME_ONLY, TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY, TENANT_SETTING_BRAND_DISPLAY_NONE, TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_NO_COLLAPSE, TENANT_SETTING_COLLAPSE_BOARDS_IN_HEADER_ALWAYS_COLLAPSE, TENANT_SETTING_LOGO_LINKS_TO_ROOT_PAGE, TENANT_SETTING_LOGO_LINKS_TO_CUSTOM_URL, TENANT_SETTING_LOGO_LINKS_TO_NOTHING, } from '../../../interfaces/ITenantSetting'; import { DangerText, SmallMutedText } from '../../common/CustomTexts'; import { getLabel, getValidationMessage } from '../../../helpers/formUtils'; import IBoardJSON from '../../../interfaces/json/IBoard'; import ActionLink from '../../common/ActionLink'; import { CancelIcon, DeleteIcon, EditIcon, LearnMoreIcon } from '../../common/Icons'; import Dropzone from '../../common/Dropzone'; export interface ISiteSettingsGeneralForm { siteName: string; siteLogo?: File; shouldDeleteSiteLogo: boolean; oldSiteLogo: string; siteFavicon?: File; shouldDeleteSiteFavicon: boolean; brandDisplaySetting: string; locale: string; useBrowserLocale: boolean; rootBoardId?: string; customDomain?: string; isPrivate: boolean; allowAnonymousFeedback: boolean; feedbackApprovalPolicy: string; allowAttachmentUpload: boolean; logoLinksTo: string; logoCustomUrl?: string; showRoadmapInHeader: boolean; collapseBoardsInHeader: string; showVoteCount: boolean; showVoteButtonInBoard: boolean; hideUnusedStatusesInFilterByStatus: boolean; showPoweredBy: boolean; } interface Props { originForm: ISiteSettingsGeneralForm; siteLogoUrl?: string; siteFaviconUrl?: string; boards: IBoardJSON[]; isMultiTenant: boolean; authenticityToken: string; areUpdating: boolean; error: string; updateTenant( siteName: string, siteLogo: File, shouldDeleteSiteLogo: boolean, oldSiteLogo: string, siteFavicon: File, shouldDeleteSiteFavicon: boolean, brandDisplaySetting: string, locale: string, useBrowserLocale: boolean, rootBoardId: number, customDomain: string, isPrivate: boolean, allowAnonymousFeedback: boolean, feedbackApprovalPolicy: string, allowAttachmentUpload: boolean, logoLinksTo: string, logoCustomUrl: string, showRoadmapInHeader: boolean, collapseBoardsInHeader: string, showVoteCount: boolean, showVoteButtonInBoard: boolean, hideUnusedStatusesInFilterByStatus: boolean, showPoweredBy: boolean, authenticityToken: string ): Promise; } const GeneralSiteSettingsP = ({ originForm, siteLogoUrl, siteFaviconUrl, boards, isMultiTenant, authenticityToken, areUpdating, error, updateTenant, }: Props) => { const { register, handleSubmit, formState: { isDirty, isSubmitSuccessful, errors }, watch, control, } = useForm({ defaultValues: { siteName: originForm.siteName, siteLogo: null, shouldDeleteSiteLogo: false, oldSiteLogo: originForm.oldSiteLogo, siteFavicon: null, shouldDeleteSiteFavicon: false, brandDisplaySetting: originForm.brandDisplaySetting, locale: originForm.locale, useBrowserLocale: originForm.useBrowserLocale, rootBoardId: originForm.rootBoardId, customDomain: originForm.customDomain, isPrivate: originForm.isPrivate, allowAnonymousFeedback: originForm.allowAnonymousFeedback, feedbackApprovalPolicy: originForm.feedbackApprovalPolicy, allowAttachmentUpload: originForm.allowAttachmentUpload, logoLinksTo: originForm.logoLinksTo, logoCustomUrl: originForm.logoCustomUrl, showRoadmapInHeader: originForm.showRoadmapInHeader, collapseBoardsInHeader: originForm.collapseBoardsInHeader, showVoteCount: originForm.showVoteCount, showVoteButtonInBoard: originForm.showVoteButtonInBoard, hideUnusedStatusesInFilterByStatus: originForm.hideUnusedStatusesInFilterByStatus, showPoweredBy: originForm.showPoweredBy, }, }); const onSubmit: SubmitHandler = data => { updateTenant( data.siteName, data.siteLogo ? data.siteLogo : null, data.shouldDeleteSiteLogo, data.oldSiteLogo, data.siteFavicon ? data.siteFavicon : null, data.shouldDeleteSiteFavicon, data.brandDisplaySetting, data.locale, data.useBrowserLocale, Number(data.rootBoardId), data.customDomain, data.isPrivate, data.allowAnonymousFeedback, data.feedbackApprovalPolicy, data.allowAttachmentUpload, data.logoLinksTo, data.logoCustomUrl, data.showRoadmapInHeader, data.collapseBoardsInHeader, data.showVoteCount, data.showVoteButtonInBoard, data.hideUnusedStatusesInFilterByStatus, data.showPoweredBy, authenticityToken ).then(res => { if (res?.status !== HttpStatus.OK) return; const urlWithoutHash = window.location.href.split('#')[0]; window.history.pushState({}, document.title, urlWithoutHash); window.location.reload(); }); }; 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); } } }, []); const customDomain = watch('customDomain'); const shouldDeleteSiteLogo = watch('shouldDeleteSiteLogo'); const shouldDeleteSiteFavicon = watch('shouldDeleteSiteFavicon'); const [showSiteLogoDropzone, setShowSiteLogoDropzone] = React.useState([null, undefined, ''].includes(siteLogoUrl)); const [showSiteFaviconDropzone, setShowSiteFaviconDropzone] = React.useState([null, undefined, ''].includes(siteFaviconUrl)); return ( <>

{ I18n.t('site_settings.general.title') }

{errors.siteName && getValidationMessage(errors.siteName.type, 'tenant', 'site_name')}
{/* Hidden oldSiteLogo field for backwards compatibility */}
{ siteLogoUrl &&
{`${originForm.siteName}
}
{ (siteLogoUrl && !shouldDeleteSiteLogo) && (showSiteLogoDropzone ? setShowSiteLogoDropzone(false)} icon={} > {I18n.t('common.buttons.cancel')} : setShowSiteLogoDropzone(true)} icon={} > {I18n.t('common.buttons.edit')} ) } { (siteLogoUrl && !showSiteLogoDropzone) && (shouldDeleteSiteLogo ? ( field.onChange(false)} icon={} > {I18n.t('common.buttons.cancel')} )} /> : ( field.onChange(true)} icon={} > {I18n.t('common.buttons.delete')} )} /> ) }
{ showSiteLogoDropzone && ( files.length > 0 ? field.onChange(files[0]) : field.onChange(null)} maxSizeKB={256} maxFiles={1} /> )} /> }
{ siteFaviconUrl &&
{`${originForm.siteName}
}
{ (siteFaviconUrl && !shouldDeleteSiteFavicon) && (showSiteFaviconDropzone ? setShowSiteFaviconDropzone(false)} icon={} > {I18n.t('common.buttons.cancel')} : setShowSiteFaviconDropzone(true)} icon={} > {I18n.t('common.buttons.edit')} ) } { (siteFaviconUrl && !showSiteFaviconDropzone) && (shouldDeleteSiteFavicon ? ( field.onChange(false)} icon={} > {I18n.t('common.buttons.cancel')} )} /> : ( field.onChange(true)} icon={} > {I18n.t('common.buttons.delete')} )} /> ) }
{ showSiteFaviconDropzone && ( files.length > 0 ? field.onChange(files[0]) : field.onChange(null)} maxSizeKB={64} maxFiles={1} accept={['image/x-icon', 'image/icon', 'image/png', 'image/jpeg', 'image/jpg']} /> )} /> }
{ I18n.t('site_settings.general.use_browser_locale_help') }
{ isMultiTenant &&
{ originForm.customDomain !== customDomain && customDomain !== '' &&
{ I18n.t('site_settings.general.custom_domain_help', { domain: customDomain }) }
}
window.open('https://docs.astuto.io/custom-domain', '_blank')} icon={} > {I18n.t('site_settings.general.custom_domain_learn_more')}
}

{ I18n.t('site_settings.general.subtitle_privacy') }

{ I18n.t('site_settings.general.is_private_help') }

{ I18n.t('site_settings.general.subtitle_moderation') }

{ I18n.t('site_settings.general.allow_anonymous_feedback_help') }
{ I18n.t('site_settings.general.feedback_approval_policy_help') }
{ I18n.t('site_settings.general.allow_attachment_upload_help') }

{ I18n.t('site_settings.general.subtitle_visibility') }

{ I18n.t('site_settings.general.show_vote_count_help') }
{ I18n.t('site_settings.general.show_vote_button_in_board_help') }
{ I18n.t('site_settings.general.hide_unused_statuses_in_filter_by_status_help') }

{I18n.t('common.buttons.update')} } /> ); } export default GeneralSiteSettingsP;