mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 03:07:52 +01:00
Add custom CSS (#264)
This commit is contained in:
committed by
GitHub
parent
653e139a9e
commit
d7e7db9f72
@@ -28,3 +28,4 @@
|
||||
@import 'components/SiteSettings/Roadmap';
|
||||
@import 'components/SiteSettings/Users';
|
||||
@import 'components/SiteSettings/Authentication';
|
||||
@import 'components/SiteSettings/Appearance/';
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
|
||||
.mutedText {
|
||||
color: $astuto-grey;
|
||||
color: var(--astuto-grey);
|
||||
}
|
||||
|
||||
.smallMutedText {
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: $astuto-light-grey;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px rgba(0, 0, 0, 0.6);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.25rem var(--primary-color-light);
|
||||
}
|
||||
|
||||
.new_user, .edit_user {
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
margin-top: 24px;
|
||||
|
||||
a { color: $primary-color; }
|
||||
a { color: var(--astuto-black); }
|
||||
}
|
||||
|
||||
.formRow {
|
||||
|
||||
@@ -54,11 +54,16 @@
|
||||
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
|
||||
a:hover { color: var(--primary-color); }
|
||||
|
||||
&.active {
|
||||
@extend .badgeLight;
|
||||
|
||||
a { color: var(--primary-color) !important };
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
@extend .badgeLight;
|
||||
}
|
||||
}
|
||||
|
||||
.profileNav {
|
||||
@@ -75,12 +80,12 @@
|
||||
}
|
||||
|
||||
.fullname {
|
||||
color: $astuto-black;
|
||||
color: var(--astuto-black);
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item:active {
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,50 @@
|
||||
::selection {
|
||||
background-color: var(--primary-color-light);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
.btnPrimary {
|
||||
@extend .btn;
|
||||
|
||||
color: white;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color-dark);
|
||||
|
||||
&:hover { color: white; }
|
||||
&:focus { box-shadow: 0 0 0 0.25rem var(--primary-color-light); }
|
||||
}
|
||||
|
||||
.btnOutlinePrimary {
|
||||
@extend .btn;
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:focus { box-shadow: 0 0 0 0.25rem var(--primary-color-light); }
|
||||
}
|
||||
|
||||
.card {
|
||||
@extend .card;
|
||||
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.04),0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||
|
||||
color: $primary-color;
|
||||
color: var(--astuto-black);
|
||||
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.card3D {
|
||||
@extend
|
||||
.card;
|
||||
@extend .card;
|
||||
|
||||
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||
|
||||
@@ -89,17 +122,17 @@
|
||||
.align-self-stretch;
|
||||
|
||||
a {
|
||||
color: $astuto-grey;
|
||||
color: var(--astuto-grey);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: $astuto-black;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: $astuto-black;
|
||||
background-color: $astuto-light-grey;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--astuto-grey-light);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +167,7 @@
|
||||
.badgeLight {
|
||||
@extend .badge-light;
|
||||
|
||||
background-color: $astuto-light-grey;
|
||||
background-color: var(--astuto-grey-light);
|
||||
}
|
||||
|
||||
.container {
|
||||
@@ -147,7 +180,7 @@
|
||||
}
|
||||
|
||||
.turbolinks-progress-bar {
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
@@ -162,6 +195,14 @@
|
||||
|
||||
& > input[type="checkbox"] {
|
||||
@extend .custom-control-input;
|
||||
|
||||
&:focus ~ label::before {
|
||||
box-shadow: 0 0 0 0.25rem var(--primary-color-light);
|
||||
}
|
||||
|
||||
&:active ~ label::before {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
& > label {
|
||||
@@ -173,14 +214,23 @@
|
||||
}
|
||||
|
||||
& > input:checked ~ label::before {
|
||||
background-color: $primary-color !important;
|
||||
border-color: $primary-color !important;
|
||||
background-color: var(--primary-color) !important;
|
||||
border-color: var(--primary-color-dark) !important;
|
||||
}
|
||||
|
||||
& > input:focus ~ label::before {
|
||||
border-color: var(--primary-color-dark) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selectPicker {
|
||||
@extend
|
||||
.custom-select;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 0.25rem var(--primary-color-light);
|
||||
border-color: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -189,13 +239,16 @@
|
||||
}
|
||||
|
||||
.actionLink {
|
||||
color: $astuto-black;
|
||||
color: var(--astuto-black);
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
margin-right: 12px;
|
||||
|
||||
&:hover { text-decoration: underline !important; }
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 4px;
|
||||
@@ -203,7 +256,7 @@
|
||||
}
|
||||
|
||||
&.actionLinkDisabled {
|
||||
color: $astuto-grey !important;
|
||||
color: var(--astuto-grey) !important;
|
||||
|
||||
text-decoration: none !important;
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.newPostContainer {
|
||||
background-color: $astuto-light-grey;
|
||||
background-color: var(--astuto-grey-light);
|
||||
text-align: center;
|
||||
|
||||
.boardTitle {
|
||||
@@ -37,6 +37,8 @@
|
||||
.postStatusListItemLink {
|
||||
@extend
|
||||
.flex-grow-1;
|
||||
|
||||
&:hover { text-decoration: none; }
|
||||
}
|
||||
|
||||
.postStatusListItem {
|
||||
@@ -84,7 +86,7 @@
|
||||
.p-3;
|
||||
|
||||
height: 140px;
|
||||
color: $primary-color;
|
||||
color: var(--astuto-black);
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
height: auto;
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
border-bottom: $like_button_size solid rgba(35,35,35,.2);
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: $primary-color;
|
||||
border-bottom-color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.likeButton.liked {
|
||||
border-bottom-color: $primary-color;
|
||||
border-bottom-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.likeCountLabel {
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
@extend
|
||||
.my-4;
|
||||
|
||||
color: $primary-color;
|
||||
color: var(--astuto-black);
|
||||
|
||||
p:last-child {
|
||||
@extend .mb-0;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
.p-0;
|
||||
|
||||
width: 32%;
|
||||
background-color: $astuto-light-grey;
|
||||
background-color: var(--astuto-grey-light);
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 100%;
|
||||
@@ -35,8 +35,7 @@
|
||||
}
|
||||
|
||||
.postLink {
|
||||
@extend
|
||||
.my-2;
|
||||
@extend .my-2;
|
||||
|
||||
&:hover { text-decoration: none; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#customCss {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 28%;
|
||||
background-color: $astuto-light-grey;
|
||||
background-color: var(--astuto-grey-light);
|
||||
overflow: hidden;
|
||||
height: 150px;
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
padding: 8px 4px;
|
||||
text-transform: uppercase;
|
||||
|
||||
.titleText { @extend .align-self-center; }
|
||||
.titleText {
|
||||
@extend .align-self-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
$primary-color: #333;
|
||||
:root {
|
||||
// Theme palette (supposed to be customized)
|
||||
--primary-color: rgb(51, 51, 51);
|
||||
--background-color: rgb(255, 255, 255);
|
||||
|
||||
$astuto-black: #333;
|
||||
$astuto-grey: rgba(0, 0, 0, 0.5);
|
||||
$astuto-light-grey: rgba(178, 178, 178, 0.2);
|
||||
// Theme palette shades (supposed to be computed from theme palette)
|
||||
--primary-color-light: color-mix(in srgb,var(--primary-color), #fff 85%);
|
||||
--primary-color-dark: color-mix(in srgb,var(--primary-color), #000 20%);
|
||||
|
||||
// Astuto colors (supposed to be fixed)
|
||||
--astuto-black: rgb(51, 51, 51);
|
||||
--astuto-grey: rgba(0, 0, 0, 0.5);
|
||||
--astuto-grey-light: rgba(178, 178, 178, 0.2);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ class SiteSettingsController < ApplicationController
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :authenticate_admin,
|
||||
only: [:general, :boards, :post_statuses, :roadmap, :authentication]
|
||||
only: [:general, :authentication, :boards, :post_statuses, :roadmap, :appearance]
|
||||
|
||||
before_action :authenticate_moderator,
|
||||
only: [:users]
|
||||
@@ -10,6 +10,9 @@ class SiteSettingsController < ApplicationController
|
||||
def general
|
||||
end
|
||||
|
||||
def authentication
|
||||
end
|
||||
|
||||
def boards
|
||||
end
|
||||
|
||||
@@ -19,9 +22,9 @@ class SiteSettingsController < ApplicationController
|
||||
def roadmap
|
||||
end
|
||||
|
||||
def users
|
||||
def appearance
|
||||
end
|
||||
|
||||
def authentication
|
||||
def users
|
||||
end
|
||||
end
|
||||
@@ -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 />;
|
||||
|
||||
@@ -26,3 +27,5 @@ export const DoneIcon = () => <MdDone />;
|
||||
export const BackIcon = () => <MdOutlineArrowBack />;
|
||||
|
||||
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:
|
||||
|
||||
@@ -3,6 +3,8 @@ class TenantSetting < ApplicationRecord
|
||||
|
||||
belongs_to :tenant
|
||||
|
||||
validates :custom_css, length: { maximum: 32000 }
|
||||
|
||||
enum brand_display: [
|
||||
:name_and_logo,
|
||||
:name_only,
|
||||
|
||||
@@ -7,7 +7,8 @@ class TenantSettingPolicy < ApplicationPolicy
|
||||
:show_vote_count,
|
||||
:show_vote_button_in_board,
|
||||
:show_roadmap_in_header,
|
||||
:collapse_boards_in_header
|
||||
:collapse_boards_in_header,
|
||||
:custom_css
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.resend_confirmation_instructions'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.resend_confirmation_instructions'), class: "btn btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.change_password'), class: "btn btn-dark btn-primary" %>
|
||||
<%= f.submit t('common.forms.auth.change_password'), class: "btnPrimary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.send_reset_password_instructions'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.send_reset_password_instructions'), class: "btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.update_profile'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.update_profile'), class: "btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.sign_up'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.sign_up'), class: "btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<% end %>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.log_in'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.log_in'), class: "btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit t('common.forms.auth.resend_unlock_instructions'), class: "btn btn-dark btn-block" %>
|
||||
<%= f.submit t('common.forms.auth.resend_unlock_instructions'), class: "btnPrimary btn-block" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -25,5 +25,11 @@
|
||||
<div class="container">
|
||||
<%= yield %>
|
||||
</div>
|
||||
|
||||
<% if @tenant and not @tenant.tenant_setting.custom_css.blank? %>
|
||||
<style type="text/css">
|
||||
<%= @tenant.tenant_setting.custom_css %>
|
||||
</style>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<%= render 'menu_link', label: t('site_settings.menu.boards'), path: site_settings_boards_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.post_statuses'), path: site_settings_post_statuses_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.roadmap'), path: site_settings_roadmap_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.appearance'), path: site_settings_appearance_path %>
|
||||
<% end %>
|
||||
|
||||
<%= render 'menu_link', label: t('site_settings.menu.users'), path: site_settings_users_path %>
|
||||
|
||||
16
app/views/site_settings/appearance.html.erb
Normal file
16
app/views/site_settings/appearance.html.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="twoColumnsContainer">
|
||||
<%= render 'menu' %>
|
||||
<div>
|
||||
<%=
|
||||
react_component(
|
||||
'SiteSettings/Appearance',
|
||||
{
|
||||
originForm: {
|
||||
customCss: @tenant_setting.custom_css,
|
||||
},
|
||||
authenticityToken: form_authenticity_token
|
||||
}
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,6 +121,7 @@ en:
|
||||
root_board_id: 'Root page'
|
||||
show_roadmap_in_header: 'Show roadmap link in header'
|
||||
collapse_boards_in_header: 'Collapse boards in header'
|
||||
custom_css: 'Custom CSS'
|
||||
user:
|
||||
email: 'Email'
|
||||
full_name: 'Full name'
|
||||
|
||||
@@ -144,6 +144,7 @@ en:
|
||||
roadmap: 'Roadmap'
|
||||
users: 'Users'
|
||||
authentication: 'Authentication'
|
||||
appearance: 'Appearance'
|
||||
info_box:
|
||||
up_to_date: 'All changes saved'
|
||||
error: 'An error occurred: %{message}'
|
||||
@@ -178,6 +179,9 @@ en:
|
||||
title2: 'Not in roadmap'
|
||||
empty: 'The roadmap is empty.'
|
||||
help: 'You can add statuses to the roadmap by dragging them from the section below. If you instead want to create a new status or change their order, go to Site settings > Statuses.'
|
||||
appearance:
|
||||
title: 'Appearance'
|
||||
learn_more: 'Learn how to customize appearance'
|
||||
users:
|
||||
title: 'Users'
|
||||
block: 'Block'
|
||||
@@ -195,6 +199,7 @@ en:
|
||||
status_deleted: 'Deleted'
|
||||
authentication:
|
||||
title: 'Authentication'
|
||||
learn_more: 'Learn how to configure custom OAuth providers'
|
||||
oauth_subtitle: 'OAuth providers'
|
||||
default_oauth: 'Default OAuth provider'
|
||||
copy_url: 'Copy URL'
|
||||
|
||||
@@ -50,11 +50,12 @@ Rails.application.routes.draw do
|
||||
|
||||
namespace :site_settings do
|
||||
get 'general'
|
||||
get 'authentication'
|
||||
get 'boards'
|
||||
get 'post_statuses'
|
||||
get 'roadmap'
|
||||
get 'appearance'
|
||||
get 'users'
|
||||
get 'authentication'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddCustomCssToTenantSettings < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :tenant_settings, :custom_css, :text, null: true
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2024_01_17_112502) do
|
||||
ActiveRecord::Schema.define(version: 2024_01_23_125448) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -134,6 +134,7 @@ ActiveRecord::Schema.define(version: 2024_01_17_112502) do
|
||||
t.integer "root_board_id", default: 0, null: false
|
||||
t.boolean "show_roadmap_in_header", default: true, null: false
|
||||
t.integer "collapse_boards_in_header", default: 0, null: false
|
||||
t.text "custom_css"
|
||||
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user