mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 03:37:56 +01:00
Add custom CSS (#264)
This commit is contained in:
committed by
GitHub
parent
653e139a9e
commit
d7e7db9f72
@@ -168,7 +168,7 @@ class NewPost extends React.Component<Props, State> {
|
||||
}
|
||||
</Button>
|
||||
:
|
||||
<a href="/users/sign_in" className="btn btn-dark">
|
||||
<a href="/users/sign_in" className="btn btnPrimary">
|
||||
{I18n.t('board.new_post.login_button')}
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
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 { getLabel } from '../../../helpers/formUtils';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import ActionLink from '../../common/ActionLink';
|
||||
import { LearnMoreIcon } from '../../common/Icons';
|
||||
|
||||
|
||||
export interface ISiteSettingsAppearanceForm {
|
||||
customCss: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
originForm: ISiteSettingsAppearanceForm;
|
||||
authenticityToken: string;
|
||||
|
||||
areUpdating: boolean;
|
||||
error: string;
|
||||
|
||||
updateTenant(
|
||||
customCss: string,
|
||||
authenticityToken: string
|
||||
): Promise<any>;
|
||||
}
|
||||
|
||||
const AppearanceSiteSettingsP = ({
|
||||
originForm,
|
||||
authenticityToken,
|
||||
|
||||
areUpdating,
|
||||
error,
|
||||
updateTenant,
|
||||
}: Props) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isDirty, isSubmitSuccessful },
|
||||
watch,
|
||||
} = useForm<ISiteSettingsAppearanceForm>({
|
||||
defaultValues: {
|
||||
customCss: originForm.customCss,
|
||||
},
|
||||
});
|
||||
|
||||
const customCss = watch('customCss');
|
||||
|
||||
const onSubmit: SubmitHandler<ISiteSettingsAppearanceForm> = data => {
|
||||
updateTenant(
|
||||
data.customCss,
|
||||
authenticityToken
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.body.appendChild(style);
|
||||
|
||||
// Clean up function
|
||||
return () => {
|
||||
document.body.removeChild(style);
|
||||
};
|
||||
}, [customCss]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<h2>{ I18n.t('site_settings.appearance.title') }</h2>
|
||||
|
||||
<p style={{textAlign: 'left'}}>
|
||||
<ActionLink
|
||||
onClick={() => window.open('https://docs.astuto.io/category/appearance-customization/', '_blank')}
|
||||
icon={<LearnMoreIcon />}
|
||||
>
|
||||
{I18n.t('site_settings.appearance.learn_more')}
|
||||
</ActionLink>
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="formRow">
|
||||
<div className="formGroup customCssForm col-12">
|
||||
<h4>{ getLabel('tenant_setting', 'custom_css') }</h4>
|
||||
|
||||
<textarea
|
||||
{...register('customCss')}
|
||||
maxLength={32000}
|
||||
id="customCss"
|
||||
className="formControl"
|
||||
onKeyDown={e => e.key === 'Tab' && e.preventDefault()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => null} disabled={!isDirty}>
|
||||
{I18n.t('common.buttons.update')}
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
|
||||
<SiteSettingsInfoBox
|
||||
areUpdating={areUpdating}
|
||||
error={error}
|
||||
areDirty={isDirty && !isSubmitSuccessful}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppearanceSiteSettingsP;
|
||||
36
app/javascript/components/SiteSettings/Appearance/index.tsx
Normal file
36
app/javascript/components/SiteSettings/Appearance/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
|
||||
import AppearanceSiteSettings from '../../../containers/AppearanceSiteSettings';
|
||||
import createStoreHelper from '../../../helpers/createStore';
|
||||
import { State } from '../../../reducers/rootReducer';
|
||||
import { ISiteSettingsAppearanceForm } from './AppearanceSiteSettingsP';
|
||||
|
||||
interface Props {
|
||||
originForm: ISiteSettingsAppearanceForm;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class AppearanceSiteSettingsRoot extends React.Component<Props> {
|
||||
store: Store<State, any>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.store = createStoreHelper();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<AppearanceSiteSettings
|
||||
originForm={this.props.originForm}
|
||||
authenticityToken={this.props.authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AppearanceSiteSettingsRoot;
|
||||
@@ -6,6 +6,8 @@ import OAuthProvidersList from './OAuthProvidersList';
|
||||
import { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
||||
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||
import ActionLink from '../../common/ActionLink';
|
||||
import { LearnMoreIcon } from '../../common/Icons';
|
||||
|
||||
interface Props {
|
||||
oAuths: OAuthsState;
|
||||
@@ -34,6 +36,15 @@ const AuthenticationIndexPage = ({
|
||||
<Box customClass="authenticationIndexPage">
|
||||
<h2>{ I18n.t('site_settings.authentication.title') }</h2>
|
||||
|
||||
<p style={{textAlign: 'left'}}>
|
||||
<ActionLink
|
||||
onClick={() => window.open('https://docs.astuto.io/category/oauth-configuration/', '_blank')}
|
||||
icon={<LearnMoreIcon />}
|
||||
>
|
||||
{I18n.t('site_settings.authentication.learn_more')}
|
||||
</ActionLink>
|
||||
</p>
|
||||
|
||||
<OAuthProvidersList
|
||||
oAuths={oAuths.items}
|
||||
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Props {
|
||||
const Button = ({ children, onClick, className = '', outline = false, disabled = false}: Props) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`${className} btn btn-${outline ? 'outline-' : ''}dark`}
|
||||
className={`${className} btn${outline ? 'Outline' : ''}Primary`}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ImCancelCircle } from 'react-icons/im';
|
||||
import { TbLock, TbLockOpen } from 'react-icons/tb';
|
||||
import { MdContentCopy, MdDone, MdOutlineArrowBack } from 'react-icons/md';
|
||||
import { GrTest } from 'react-icons/gr';
|
||||
import { MdOutlineLibraryBooks } from "react-icons/md";
|
||||
|
||||
export const EditIcon = () => <FiEdit />;
|
||||
|
||||
@@ -25,4 +26,6 @@ export const DoneIcon = () => <MdDone />;
|
||||
|
||||
export const BackIcon = () => <MdOutlineArrowBack />;
|
||||
|
||||
export const ReplyIcon = () => <BsReply />;
|
||||
export const ReplyIcon = () => <BsReply />;
|
||||
|
||||
export const LearnMoreIcon = () => <MdOutlineLibraryBooks />;
|
||||
29
app/javascript/containers/AppearanceSiteSettings.tsx
Normal file
29
app/javascript/containers/AppearanceSiteSettings.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import AppearanceSiteSettingsP from "../components/SiteSettings/Appearance/AppearanceSiteSettingsP";
|
||||
import { updateTenant } from "../actions/Tenant/updateTenant";
|
||||
import { State } from "../reducers/rootReducer";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
areUpdating: state.siteSettings.appearance.areUpdating,
|
||||
error: state.siteSettings.appearance.error,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
updateTenant(
|
||||
customCss: string,
|
||||
authenticityToken: string,
|
||||
): Promise<any> {
|
||||
return dispatch(updateTenant({
|
||||
tenantSetting: {
|
||||
custom_css: customCss,
|
||||
},
|
||||
authenticityToken,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AppearanceSiteSettingsP);
|
||||
@@ -26,6 +26,7 @@ interface ITenantSetting {
|
||||
show_vote_button_in_board?: boolean;
|
||||
show_roadmap_in_header?: boolean;
|
||||
collapse_boards_in_header?: TenantSettingCollapseBoardsInHeader;
|
||||
custom_css?: string;
|
||||
}
|
||||
|
||||
export default ITenantSetting;
|
||||
48
app/javascript/reducers/SiteSettings/appearanceReducer.ts
Normal file
48
app/javascript/reducers/SiteSettings/appearanceReducer.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
TenantUpdateActionTypes,
|
||||
TENANT_UPDATE_START,
|
||||
TENANT_UPDATE_SUCCESS,
|
||||
TENANT_UPDATE_FAILURE,
|
||||
} from '../../actions/Tenant/updateTenant';
|
||||
|
||||
export interface SiteSettingsAppearanceState {
|
||||
areUpdating: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: SiteSettingsAppearanceState = {
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
const siteSettingsAppearanceReducer = (
|
||||
state = initialState,
|
||||
action: TenantUpdateActionTypes,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case TENANT_UPDATE_START:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: true,
|
||||
};
|
||||
|
||||
case TENANT_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
case TENANT_UPDATE_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default siteSettingsAppearanceReducer;
|
||||
@@ -95,6 +95,7 @@ import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from '
|
||||
import siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
|
||||
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
|
||||
import siteSettingsAuthenticationReducer, { SiteSettingsAuthenticationState } from './SiteSettings/authenticationReducer';
|
||||
import siteSettingsAppearanceReducer, { SiteSettingsAppearanceState } from './SiteSettings/appearanceReducer';
|
||||
|
||||
interface SiteSettingsState {
|
||||
general: SiteSettingsGeneralState;
|
||||
@@ -102,6 +103,7 @@ interface SiteSettingsState {
|
||||
boards: SiteSettingsBoardsState;
|
||||
postStatuses: SiteSettingsPostStatusesState;
|
||||
roadmap: SiteSettingsRoadmapState;
|
||||
appearance: SiteSettingsAppearanceState;
|
||||
users: SiteSettingsUsersState;
|
||||
}
|
||||
|
||||
@@ -111,6 +113,7 @@ const initialState: SiteSettingsState = {
|
||||
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
||||
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
||||
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
||||
appearance: siteSettingsAppearanceReducer(undefined, {} as any),
|
||||
users: siteSettingsUsersReducer(undefined, {} as any),
|
||||
};
|
||||
|
||||
@@ -138,6 +141,7 @@ const siteSettingsReducer = (
|
||||
return {
|
||||
...state,
|
||||
general: siteSettingsGeneralReducer(state.general, action),
|
||||
appearance: siteSettingsAppearanceReducer(state.general, action),
|
||||
};
|
||||
|
||||
case OAUTH_SUBMIT_START:
|
||||
|
||||
Reference in New Issue
Block a user