mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 11:47:56 +01:00
Add site favicon attachment
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
@extend .mb-4;
|
@extend .mb-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.siteLogoPreview {
|
.siteLogoPreview, .siteFaviconPreview {
|
||||||
@extend .d-block, .my-2;
|
@extend .d-block, .my-2;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -18,21 +18,19 @@
|
|||||||
height: fit-content; /* Adjusts height to match the image */
|
height: fit-content; /* Adjusts height to match the image */
|
||||||
}
|
}
|
||||||
|
|
||||||
.siteLogoPreview .siteLogoPreviewImg {
|
.siteLogoPreview .siteLogoPreviewImg, .siteFaviconPreview .siteFaviconPreviewImg {
|
||||||
display: block;
|
display: block;
|
||||||
height: 50px; /* Fixed height for the image */
|
height: 50px; /* Fixed height for the image */
|
||||||
width: auto; /* Maintain the aspect ratio */
|
width: auto; /* Maintain the aspect ratio */
|
||||||
}
|
}
|
||||||
|
|
||||||
.siteLogoPreview.siteLogoPreviewShouldDelete {
|
.siteLogoPreview.siteLogoPreviewShouldDelete, .siteFaviconPreview.siteFaviconPreviewShouldDelete {
|
||||||
border: 2px solid red;
|
border: 2px solid red;
|
||||||
|
|
||||||
.siteLogoPreviewImg {
|
.siteFaviconPreviewImg {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.siteLogoActions {
|
.siteLogoActions, .siteFaviconActions { @extend .d-flex; }
|
||||||
@extend .d-flex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -96,14 +96,22 @@ class TenantsController < ApplicationController
|
|||||||
# to avoid unique constraint violation
|
# to avoid unique constraint violation
|
||||||
params[:tenant][:custom_domain] = nil if params[:tenant][:custom_domain].blank?
|
params[:tenant][:custom_domain] = nil if params[:tenant][:custom_domain].blank?
|
||||||
|
|
||||||
|
# Handle site logo attachment
|
||||||
if params[:tenant][:should_delete_site_logo] == "true"
|
if params[:tenant][:should_delete_site_logo] == "true"
|
||||||
@tenant.site_logo.purge if @tenant.site_logo.attached?
|
@tenant.site_logo.purge if @tenant.site_logo.attached?
|
||||||
elsif params[:tenant][:site_logo].present?
|
elsif params[:tenant][:site_logo].present?
|
||||||
# If site_logo is provided, remove the old one if it exists and attach the new one
|
|
||||||
@tenant.site_logo.purge if @tenant.site_logo.attached?
|
@tenant.site_logo.purge if @tenant.site_logo.attached?
|
||||||
@tenant.site_logo.attach(params[:tenant][:site_logo])
|
@tenant.site_logo.attach(params[:tenant][:site_logo])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Handle site favicon attachment
|
||||||
|
if params[:tenant][:should_delete_site_favicon] == "true"
|
||||||
|
@tenant.site_favicon.purge if @tenant.site_favicon.attached?
|
||||||
|
elsif params[:tenant][:site_favicon].present?
|
||||||
|
@tenant.site_favicon.purge if @tenant.site_favicon.attached?
|
||||||
|
@tenant.site_favicon.attach(params[:tenant][:site_favicon])
|
||||||
|
end
|
||||||
|
|
||||||
if @tenant.update(tenant_update_params)
|
if @tenant.update(tenant_update_params)
|
||||||
render json: @tenant
|
render json: @tenant
|
||||||
else
|
else
|
||||||
@@ -141,7 +149,7 @@ class TenantsController < ApplicationController
|
|||||||
.permitted_attributes_for_update
|
.permitted_attributes_for_update
|
||||||
.concat([{
|
.concat([{
|
||||||
tenant_setting_attributes: policy(@tenant.tenant_setting).permitted_attributes_for_update,
|
tenant_setting_attributes: policy(@tenant.tenant_setting).permitted_attributes_for_update,
|
||||||
additional_params: [:should_delete_site_logo]
|
additional_params: [:should_delete_site_logo, :should_delete_site_favicon]
|
||||||
}]) # in order to permit nested attributes for tenant_setting
|
}]) # in order to permit nested attributes for tenant_setting
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Action } from "redux";
|
|||||||
import { ThunkAction } from "redux-thunk";
|
import { ThunkAction } from "redux-thunk";
|
||||||
|
|
||||||
import HttpStatus from "../../constants/http_status";
|
import HttpStatus from "../../constants/http_status";
|
||||||
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
|
||||||
import ITenantSetting from "../../interfaces/ITenantSetting";
|
import ITenantSetting from "../../interfaces/ITenantSetting";
|
||||||
import ITenantJSON from "../../interfaces/json/ITenant";
|
import ITenantJSON from "../../interfaces/json/ITenant";
|
||||||
import { State } from "../../reducers/rootReducer";
|
import { State } from "../../reducers/rootReducer";
|
||||||
@@ -50,6 +49,8 @@ interface UpdateTenantParams {
|
|||||||
siteLogo?: File;
|
siteLogo?: File;
|
||||||
shouldDeleteSiteLogo?: boolean;
|
shouldDeleteSiteLogo?: boolean;
|
||||||
oldSiteLogo?: string;
|
oldSiteLogo?: string;
|
||||||
|
siteFavicon?: File;
|
||||||
|
shouldDeleteSiteFavicon?: boolean;
|
||||||
tenantSetting?: ITenantSetting;
|
tenantSetting?: ITenantSetting;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
customDomain?: string;
|
customDomain?: string;
|
||||||
@@ -61,6 +62,8 @@ export const updateTenant = ({
|
|||||||
siteLogo = null,
|
siteLogo = null,
|
||||||
shouldDeleteSiteLogo = null,
|
shouldDeleteSiteLogo = null,
|
||||||
oldSiteLogo = null,
|
oldSiteLogo = null,
|
||||||
|
siteFavicon = null,
|
||||||
|
shouldDeleteSiteFavicon = null,
|
||||||
tenantSetting = null,
|
tenantSetting = null,
|
||||||
locale = null,
|
locale = null,
|
||||||
customDomain = null,
|
customDomain = null,
|
||||||
@@ -79,6 +82,10 @@ export const updateTenant = ({
|
|||||||
body.append('tenant[should_delete_site_logo]', shouldDeleteSiteLogo.toString());
|
body.append('tenant[should_delete_site_logo]', shouldDeleteSiteLogo.toString());
|
||||||
if (oldSiteLogo)
|
if (oldSiteLogo)
|
||||||
body.append('tenant[old_site_logo]', oldSiteLogo);
|
body.append('tenant[old_site_logo]', oldSiteLogo);
|
||||||
|
if (siteFavicon)
|
||||||
|
body.append('tenant[site_favicon]', siteFavicon);
|
||||||
|
if (shouldDeleteSiteFavicon)
|
||||||
|
body.append('tenant[should_delete_site_favicon]', shouldDeleteSiteFavicon.toString());
|
||||||
if (locale)
|
if (locale)
|
||||||
body.append('tenant[locale]', locale);
|
body.append('tenant[locale]', locale);
|
||||||
if (customDomain)
|
if (customDomain)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface ISiteSettingsGeneralForm {
|
|||||||
siteName: string;
|
siteName: string;
|
||||||
siteLogo: File;
|
siteLogo: File;
|
||||||
oldSiteLogo: string;
|
oldSiteLogo: string;
|
||||||
|
siteFavicon: File;
|
||||||
brandDisplaySetting: string;
|
brandDisplaySetting: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
useBrowserLocale: boolean;
|
useBrowserLocale: boolean;
|
||||||
@@ -49,6 +50,7 @@ export interface ISiteSettingsGeneralForm {
|
|||||||
interface Props {
|
interface Props {
|
||||||
originForm: ISiteSettingsGeneralForm;
|
originForm: ISiteSettingsGeneralForm;
|
||||||
siteLogoUrl?: string;
|
siteLogoUrl?: string;
|
||||||
|
siteFaviconUrl?: string;
|
||||||
boards: IBoardJSON[];
|
boards: IBoardJSON[];
|
||||||
isMultiTenant: boolean;
|
isMultiTenant: boolean;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
@@ -61,6 +63,8 @@ interface Props {
|
|||||||
siteLogo: File,
|
siteLogo: File,
|
||||||
shouldDeleteSiteLogo: boolean,
|
shouldDeleteSiteLogo: boolean,
|
||||||
oldSiteLogo: string,
|
oldSiteLogo: string,
|
||||||
|
siteFavicon: File,
|
||||||
|
shouldDeleteSiteFavicon: boolean,
|
||||||
brandDisplaySetting: string,
|
brandDisplaySetting: string,
|
||||||
locale: string,
|
locale: string,
|
||||||
useBrowserLocale: boolean,
|
useBrowserLocale: boolean,
|
||||||
@@ -84,6 +88,7 @@ interface Props {
|
|||||||
const GeneralSiteSettingsP = ({
|
const GeneralSiteSettingsP = ({
|
||||||
originForm,
|
originForm,
|
||||||
siteLogoUrl,
|
siteLogoUrl,
|
||||||
|
siteFaviconUrl,
|
||||||
boards,
|
boards,
|
||||||
isMultiTenant,
|
isMultiTenant,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
@@ -132,6 +137,8 @@ const GeneralSiteSettingsP = ({
|
|||||||
'siteLogo' in data && data.siteLogo ? data.siteLogo[0] : null,
|
'siteLogo' in data && data.siteLogo ? data.siteLogo[0] : null,
|
||||||
shouldDeleteSiteLogo,
|
shouldDeleteSiteLogo,
|
||||||
data.oldSiteLogo,
|
data.oldSiteLogo,
|
||||||
|
'siteFavicon' in data && data.siteFavicon ? data.siteFavicon[0] : null,
|
||||||
|
shouldDeleteSiteFavicon,
|
||||||
data.brandDisplaySetting,
|
data.brandDisplaySetting,
|
||||||
data.locale,
|
data.locale,
|
||||||
data.useBrowserLocale,
|
data.useBrowserLocale,
|
||||||
@@ -182,6 +189,11 @@ const GeneralSiteSettingsP = ({
|
|||||||
const [showSiteLogoDropzone, setShowSiteLogoDropzone] = React.useState([null, undefined, ''].includes(siteLogoUrl));
|
const [showSiteLogoDropzone, setShowSiteLogoDropzone] = React.useState([null, undefined, ''].includes(siteLogoUrl));
|
||||||
const [shouldDeleteSiteLogo, setShouldDeleteSiteLogo] = React.useState(false);
|
const [shouldDeleteSiteLogo, setShouldDeleteSiteLogo] = React.useState(false);
|
||||||
|
|
||||||
|
// Site favicon
|
||||||
|
const [siteFaviconFile, setSiteFaviconFile] = React.useState<any>([]);
|
||||||
|
const [showSiteFaviconDropzone, setShowSiteFaviconDropzone] = React.useState([null, undefined, ''].includes(siteFaviconUrl));
|
||||||
|
const [shouldDeleteSiteFavicon, setShouldDeleteSiteFavicon] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box customClass="generalSiteSettingsContainer">
|
<Box customClass="generalSiteSettingsContainer">
|
||||||
@@ -293,7 +305,7 @@ const GeneralSiteSettingsP = ({
|
|||||||
files={siteLogoFile}
|
files={siteLogoFile}
|
||||||
setFiles={setSiteLogoFile}
|
setFiles={setSiteLogoFile}
|
||||||
onDrop={field.onChange}
|
onDrop={field.onChange}
|
||||||
maxSizeKB={512}
|
maxSizeKB={256}
|
||||||
maxFiles={1}
|
maxFiles={1}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -302,8 +314,72 @@ const GeneralSiteSettingsP = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="formGroup col-6">
|
<div className="formGroup col-6">
|
||||||
<label htmlFor="siteFavicon">{ getLabel('tenant', 'site_logo') }</label>
|
<label htmlFor="siteFavicon">{ getLabel('tenant', 'site_favicon') }</label>
|
||||||
{/* <Dropzone /> */}
|
|
||||||
|
{
|
||||||
|
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 ?
|
||||||
|
<ActionLink
|
||||||
|
onClick={() => setShouldDeleteSiteFavicon(false)}
|
||||||
|
icon={<CancelIcon />}
|
||||||
|
>
|
||||||
|
{I18n.t('common.buttons.cancel')}
|
||||||
|
</ActionLink>
|
||||||
|
:
|
||||||
|
<ActionLink
|
||||||
|
onClick={() => { setShouldDeleteSiteFavicon(true); setValue('siteFavicon', getValues('siteFavicon'), { shouldDirty: true }) }}
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
>
|
||||||
|
{I18n.t('common.buttons.delete')}
|
||||||
|
</ActionLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
showSiteFaviconDropzone &&
|
||||||
|
<Controller
|
||||||
|
name="siteFavicon"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Dropzone
|
||||||
|
files={siteFaviconFile}
|
||||||
|
setFiles={setSiteFaviconFile}
|
||||||
|
onDrop={field.onChange}
|
||||||
|
maxSizeKB={64}
|
||||||
|
maxFiles={1}
|
||||||
|
accept={['image/x-icon', 'image/icon', 'image/png', 'image/jpeg', 'image/jpg']}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ISiteSettingsGeneralForm } from './GeneralSiteSettingsP';
|
|||||||
interface Props {
|
interface Props {
|
||||||
originForm: ISiteSettingsGeneralForm;
|
originForm: ISiteSettingsGeneralForm;
|
||||||
siteLogoUrl?: string;
|
siteLogoUrl?: string;
|
||||||
|
siteFaviconUrl?: string;
|
||||||
boards: IBoardJSON[];
|
boards: IBoardJSON[];
|
||||||
isMultiTenant: boolean;
|
isMultiTenant: boolean;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
@@ -31,6 +32,7 @@ class GeneralSiteSettingsRoot extends React.Component<Props> {
|
|||||||
<GeneralSiteSettings
|
<GeneralSiteSettings
|
||||||
originForm={this.props.originForm}
|
originForm={this.props.originForm}
|
||||||
siteLogoUrl={this.props.siteLogoUrl}
|
siteLogoUrl={this.props.siteLogoUrl}
|
||||||
|
siteFaviconUrl={this.props.siteFaviconUrl}
|
||||||
boards={this.props.boards}
|
boards={this.props.boards}
|
||||||
isMultiTenant={this.props.isMultiTenant}
|
isMultiTenant={this.props.isMultiTenant}
|
||||||
authenticityToken={this.props.authenticityToken}
|
authenticityToken={this.props.authenticityToken}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface Props {
|
|||||||
onDrop?: any;
|
onDrop?: any;
|
||||||
maxSizeKB?: number;
|
maxSizeKB?: number;
|
||||||
maxFiles?: number;
|
maxFiles?: number;
|
||||||
|
accept?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dropzone = ({
|
const Dropzone = ({
|
||||||
@@ -17,18 +18,20 @@ const Dropzone = ({
|
|||||||
onDrop,
|
onDrop,
|
||||||
maxSizeKB = 256,
|
maxSizeKB = 256,
|
||||||
maxFiles = 1,
|
maxFiles = 1,
|
||||||
|
accept = ['image/png', 'image/jpeg', 'image/jpg', 'image/x-icon', 'image/icon', 'image/svg+xml', 'image/svg', 'image/webp'],
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const acceptDict = accept.reduce((acc, type) => {
|
||||||
|
acc[type] = [];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getRootProps,
|
getRootProps,
|
||||||
getInputProps,
|
getInputProps,
|
||||||
isDragAccept,
|
isDragAccept,
|
||||||
isDragReject
|
isDragReject
|
||||||
} = useDropzone({
|
} = useDropzone({
|
||||||
accept: {
|
accept: acceptDict,
|
||||||
'image/png': [],
|
|
||||||
'image/jpeg': [],
|
|
||||||
'image/jpg': [],
|
|
||||||
},
|
|
||||||
maxSize: maxSizeKB * 1024,
|
maxSize: maxSizeKB * 1024,
|
||||||
maxFiles: maxFiles,
|
maxFiles: maxFiles,
|
||||||
onDrop: (acceptedFiles, fileRejections) => {
|
onDrop: (acceptedFiles, fileRejections) => {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
siteLogo: File,
|
siteLogo: File,
|
||||||
shouldDeleteSiteLogo: boolean,
|
shouldDeleteSiteLogo: boolean,
|
||||||
oldSiteLogo: string,
|
oldSiteLogo: string,
|
||||||
|
siteFavicon: File,
|
||||||
|
shouldDeleteSiteFavicon: boolean,
|
||||||
brandDisplaySetting: TenantSettingBrandDisplay,
|
brandDisplaySetting: TenantSettingBrandDisplay,
|
||||||
locale: string,
|
locale: string,
|
||||||
useBrowserLocale: boolean,
|
useBrowserLocale: boolean,
|
||||||
@@ -44,6 +46,8 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
siteLogo,
|
siteLogo,
|
||||||
shouldDeleteSiteLogo,
|
shouldDeleteSiteLogo,
|
||||||
oldSiteLogo,
|
oldSiteLogo,
|
||||||
|
siteFavicon,
|
||||||
|
shouldDeleteSiteFavicon,
|
||||||
tenantSetting: {
|
tenantSetting: {
|
||||||
brand_display: brandDisplaySetting,
|
brand_display: brandDisplaySetting,
|
||||||
use_browser_locale: useBrowserLocale,
|
use_browser_locale: useBrowserLocale,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class Tenant < ApplicationRecord
|
|||||||
has_many :default_o_auths, -> { where tenant_id: nil, is_enabled: true }, through: :tenant_default_o_auths, source: :o_auth
|
has_many :default_o_auths, -> { where tenant_id: nil, is_enabled: true }, through: :tenant_default_o_auths, source: :o_auth
|
||||||
|
|
||||||
has_one_attached :site_logo, service: ENV.fetch('ACTIVE_STORAGE_PUBLIC_SERVICE', :local).to_sym
|
has_one_attached :site_logo, service: ENV.fetch('ACTIVE_STORAGE_PUBLIC_SERVICE', :local).to_sym
|
||||||
|
has_one_attached :site_favicon, service: ENV.fetch('ACTIVE_STORAGE_PUBLIC_SERVICE', :local).to_sym
|
||||||
|
|
||||||
enum status: [:active, :pending, :blocked]
|
enum status: [:active, :pending, :blocked]
|
||||||
|
|
||||||
@@ -23,6 +24,12 @@ class Tenant < ApplicationRecord
|
|||||||
validates :subdomain, presence: true, uniqueness: true
|
validates :subdomain, presence: true, uniqueness: true
|
||||||
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }
|
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }
|
||||||
validates :custom_domain, format: { with: /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/ }, uniqueness: true, allow_blank: true, allow_nil: true
|
validates :custom_domain, format: { with: /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/ }, uniqueness: true, allow_blank: true, allow_nil: true
|
||||||
|
validates :site_logo,
|
||||||
|
content_type: Rails.application.accepted_image_types,
|
||||||
|
size: { less_than: 256.kilobytes }
|
||||||
|
validates :site_favicon,
|
||||||
|
content_type: Rails.application.accepted_image_types,
|
||||||
|
size: { less_than: 64.kilobytes }
|
||||||
|
|
||||||
accepts_nested_attributes_for :tenant_setting, update_only: true
|
accepts_nested_attributes_for :tenant_setting, update_only: true
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class User < ApplicationRecord
|
|||||||
validates :password, allow_blank: true, length: { in: 6..128 }
|
validates :password, allow_blank: true, length: { in: 6..128 }
|
||||||
validates :password, presence: true, on: :create
|
validates :password, presence: true, on: :create
|
||||||
validates :avatar,
|
validates :avatar,
|
||||||
content_type: ['image/png', 'image/jpg', 'image/jpeg'],
|
content_type: Rails.application.accepted_image_types,
|
||||||
size: { less_than: 100.kilobytes }
|
size: { less_than: 128.kilobytes }
|
||||||
|
|
||||||
def set_default_role
|
def set_default_role
|
||||||
self.role ||= :user
|
self.role ||= :user
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class TenantPolicy < ApplicationPolicy
|
|||||||
|
|
||||||
def permitted_attributes_for_update
|
def permitted_attributes_for_update
|
||||||
if user.admin?
|
if user.admin?
|
||||||
[:site_name, :site_logo, :old_site_logo, :locale, :custom_domain]
|
[:site_name, :site_logo, :old_site_logo, :site_favicon, :locale, :custom_domain]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<%= javascript_include_tag "application", "data-turbo-track": "reload" %>
|
<%= javascript_include_tag "application", "data-turbo-track": "reload" %>
|
||||||
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
||||||
|
|
||||||
<%= favicon_link_tag asset_path('favicon.png') %>
|
<%= favicon_link_tag @tenant.site_favicon.attached? ? url_for(@tenant.site_favicon) : asset_path('favicon.png') %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
useBrowserLocale: @tenant_setting.use_browser_locale,
|
useBrowserLocale: @tenant_setting.use_browser_locale,
|
||||||
},
|
},
|
||||||
siteLogoUrl: @tenant.site_logo.attached? ? url_for(@tenant.site_logo) : nil,
|
siteLogoUrl: @tenant.site_logo.attached? ? url_for(@tenant.site_logo) : nil,
|
||||||
|
siteFaviconUrl: @tenant.site_favicon.attached? ? url_for(@tenant.site_favicon) : nil,
|
||||||
boards: @tenant.boards.order(order: :asc),
|
boards: @tenant.boards.order(order: :asc),
|
||||||
isMultiTenant: Rails.application.multi_tenancy?,
|
isMultiTenant: Rails.application.multi_tenancy?,
|
||||||
authenticityToken: form_authenticity_token
|
authenticityToken: form_authenticity_token
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ module App
|
|||||||
15
|
15
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accepted_image_types
|
||||||
|
%w[image/png image/jpg image/jpeg image/x-icon image/icon image/svg+xml image/svg image/webp]
|
||||||
|
end
|
||||||
|
|
||||||
def trial_period_days
|
def trial_period_days
|
||||||
ENV.key?("TRIAL_PERIOD_DAYS") ? ENV["TRIAL_PERIOD_DAYS"].to_i.days : 7.days
|
ENV.key?("TRIAL_PERIOD_DAYS") ? ENV["TRIAL_PERIOD_DAYS"].to_i.days : 7.days
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ en:
|
|||||||
tenant:
|
tenant:
|
||||||
site_name: 'Site name'
|
site_name: 'Site name'
|
||||||
site_logo: 'Site logo'
|
site_logo: 'Site logo'
|
||||||
|
site_favicon: 'Site favicon'
|
||||||
subdomain: 'Subdomain'
|
subdomain: 'Subdomain'
|
||||||
locale: 'Language'
|
locale: 'Language'
|
||||||
custom_domain: 'Custom domain'
|
custom_domain: 'Custom domain'
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ en:
|
|||||||
user_staff: 'Staff'
|
user_staff: 'Staff'
|
||||||
language_supported: '%{language} supported'
|
language_supported: '%{language} supported'
|
||||||
powered_by: 'Powered by'
|
powered_by: 'Powered by'
|
||||||
drag_and_drop: 'Drag and drop or click to upload images (only .png or .jpg, max %{maxCount} files, max %{maxSize}KB each)'
|
drag_and_drop: 'Drag and drop or click to upload images (max %{maxCount} files, max %{maxSize}KB each)'
|
||||||
buttons:
|
buttons:
|
||||||
new: 'New'
|
new: 'New'
|
||||||
edit: 'Edit'
|
edit: 'Edit'
|
||||||
|
|||||||
Reference in New Issue
Block a user