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
@@ -27,4 +27,5 @@
|
|||||||
@import 'components/SiteSettings/PostStatuses';
|
@import 'components/SiteSettings/PostStatuses';
|
||||||
@import 'components/SiteSettings/Roadmap';
|
@import 'components/SiteSettings/Roadmap';
|
||||||
@import 'components/SiteSettings/Users';
|
@import 'components/SiteSettings/Users';
|
||||||
@import 'components/SiteSettings/Authentication';
|
@import 'components/SiteSettings/Authentication';
|
||||||
|
@import 'components/SiteSettings/Appearance/';
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mutedText {
|
.mutedText {
|
||||||
color: $astuto-grey;
|
color: var(--astuto-grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
.smallMutedText {
|
.smallMutedText {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus {
|
.form-control:focus {
|
||||||
border-color: $astuto-light-grey;
|
border-color: var(--primary-color);
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 0 0 0.25rem var(--primary-color-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.new_user, .edit_user {
|
.new_user, .edit_user {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
|
||||||
a { color: $primary-color; }
|
a { color: var(--astuto-black); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.formRow {
|
.formRow {
|
||||||
|
|||||||
@@ -54,11 +54,16 @@
|
|||||||
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
a:hover { color: var(--primary-color); }
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
@extend .badgeLight;
|
||||||
|
|
||||||
|
a { color: var(--primary-color) !important };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item.active {
|
|
||||||
@extend .badgeLight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profileNav {
|
.profileNav {
|
||||||
@@ -75,12 +80,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fullname {
|
.fullname {
|
||||||
color: $astuto-black;
|
color: var(--astuto-black);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item:active {
|
.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 {
|
.card {
|
||||||
@extend .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);
|
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);
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
|
||||||
color: $primary-color;
|
color: var(--astuto-black);
|
||||||
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card3D {
|
.card3D {
|
||||||
@extend
|
@extend .card;
|
||||||
.card;
|
|
||||||
|
|
||||||
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
|
||||||
@@ -89,17 +122,17 @@
|
|||||||
.align-self-stretch;
|
.align-self-stretch;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $astuto-grey;
|
color: var(--astuto-grey);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $astuto-black;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link.active {
|
.nav-link.active {
|
||||||
color: $astuto-black;
|
color: var(--primary-color);
|
||||||
background-color: $astuto-light-grey;
|
background-color: var(--astuto-grey-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +167,7 @@
|
|||||||
.badgeLight {
|
.badgeLight {
|
||||||
@extend .badge-light;
|
@extend .badge-light;
|
||||||
|
|
||||||
background-color: $astuto-light-grey;
|
background-color: var(--astuto-grey-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -147,7 +180,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.turbolinks-progress-bar {
|
.turbolinks-progress-bar {
|
||||||
background-color: $primary-color;
|
background-color: var(--primary-color);
|
||||||
height: 2px;
|
height: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +195,14 @@
|
|||||||
|
|
||||||
& > input[type="checkbox"] {
|
& > input[type="checkbox"] {
|
||||||
@extend .custom-control-input;
|
@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 {
|
& > label {
|
||||||
@@ -173,14 +214,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > input:checked ~ label::before {
|
& > input:checked ~ label::before {
|
||||||
background-color: $primary-color !important;
|
background-color: var(--primary-color) !important;
|
||||||
border-color: $primary-color !important;
|
border-color: var(--primary-color-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > input:focus ~ label::before {
|
||||||
|
border-color: var(--primary-color-dark) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectPicker {
|
.selectPicker {
|
||||||
@extend
|
@extend
|
||||||
.custom-select;
|
.custom-select;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem var(--primary-color-light);
|
||||||
|
border-color: var(--primary-color-dark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
@@ -189,13 +239,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.actionLink {
|
.actionLink {
|
||||||
color: $astuto-black;
|
color: var(--astuto-black);
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
|
||||||
&:hover { text-decoration: underline !important; }
|
&:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
@@ -203,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.actionLinkDisabled {
|
&.actionLinkDisabled {
|
||||||
color: $astuto-grey !important;
|
color: var(--astuto-grey) !important;
|
||||||
|
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.newPostContainer {
|
.newPostContainer {
|
||||||
background-color: $astuto-light-grey;
|
background-color: var(--astuto-grey-light);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.boardTitle {
|
.boardTitle {
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
.postStatusListItemLink {
|
.postStatusListItemLink {
|
||||||
@extend
|
@extend
|
||||||
.flex-grow-1;
|
.flex-grow-1;
|
||||||
|
|
||||||
|
&:hover { text-decoration: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.postStatusListItem {
|
.postStatusListItem {
|
||||||
@@ -84,7 +86,7 @@
|
|||||||
.p-3;
|
.p-3;
|
||||||
|
|
||||||
height: 140px;
|
height: 140px;
|
||||||
color: $primary-color;
|
color: var(--astuto-black);
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
border-bottom: $like_button_size solid rgba(35,35,35,.2);
|
border-bottom: $like_button_size solid rgba(35,35,35,.2);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-bottom-color: $primary-color;
|
border-bottom-color: var(--primary-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.likeButton.liked {
|
.likeButton.liked {
|
||||||
border-bottom-color: $primary-color;
|
border-bottom-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.likeCountLabel {
|
.likeCountLabel {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
@extend
|
@extend
|
||||||
.my-4;
|
.my-4;
|
||||||
|
|
||||||
color: $primary-color;
|
color: var(--astuto-black);
|
||||||
|
|
||||||
p:last-child {
|
p:last-child {
|
||||||
@extend .mb-0;
|
@extend .mb-0;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
.p-0;
|
.p-0;
|
||||||
|
|
||||||
width: 32%;
|
width: 32%;
|
||||||
background-color: $astuto-light-grey;
|
background-color: var(--astuto-grey-light);
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -35,8 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.postLink {
|
.postLink {
|
||||||
@extend
|
@extend .my-2;
|
||||||
.my-2;
|
|
||||||
|
|
||||||
&:hover { text-decoration: none; }
|
&:hover { text-decoration: none; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#customCss {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex: 0 0 28%;
|
flex: 0 0 28%;
|
||||||
background-color: $astuto-light-grey;
|
background-color: var(--astuto-grey-light);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
|
|
||||||
@@ -47,7 +47,9 @@
|
|||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
text-transform: uppercase;
|
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;
|
// Theme palette shades (supposed to be computed from theme palette)
|
||||||
$astuto-grey: rgba(0, 0, 0, 0.5);
|
--primary-color-light: color-mix(in srgb,var(--primary-color), #fff 85%);
|
||||||
$astuto-light-grey: rgba(178, 178, 178, 0.2);
|
--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
|
include ApplicationHelper
|
||||||
|
|
||||||
before_action :authenticate_admin,
|
before_action :authenticate_admin,
|
||||||
only: [:general, :boards, :post_statuses, :roadmap, :authentication]
|
only: [:general, :authentication, :boards, :post_statuses, :roadmap, :appearance]
|
||||||
|
|
||||||
before_action :authenticate_moderator,
|
before_action :authenticate_moderator,
|
||||||
only: [:users]
|
only: [:users]
|
||||||
@@ -10,6 +10,9 @@ class SiteSettingsController < ApplicationController
|
|||||||
def general
|
def general
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authentication
|
||||||
|
end
|
||||||
|
|
||||||
def boards
|
def boards
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -19,9 +22,9 @@ class SiteSettingsController < ApplicationController
|
|||||||
def roadmap
|
def roadmap
|
||||||
end
|
end
|
||||||
|
|
||||||
def users
|
def appearance
|
||||||
end
|
end
|
||||||
|
|
||||||
def authentication
|
def users
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -168,7 +168,7 @@ class NewPost extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
</Button>
|
</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')}
|
{I18n.t('board.new_post.login_button')}
|
||||||
</a>
|
</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 { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
||||||
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
||||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||||
|
import ActionLink from '../../common/ActionLink';
|
||||||
|
import { LearnMoreIcon } from '../../common/Icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
oAuths: OAuthsState;
|
oAuths: OAuthsState;
|
||||||
@@ -34,6 +36,15 @@ const AuthenticationIndexPage = ({
|
|||||||
<Box customClass="authenticationIndexPage">
|
<Box customClass="authenticationIndexPage">
|
||||||
<h2>{ I18n.t('site_settings.authentication.title') }</h2>
|
<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
|
<OAuthProvidersList
|
||||||
oAuths={oAuths.items}
|
oAuths={oAuths.items}
|
||||||
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
const Button = ({ children, onClick, className = '', outline = false, disabled = false}: Props) => (
|
const Button = ({ children, onClick, className = '', outline = false, disabled = false}: Props) => (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`${className} btn btn-${outline ? 'outline-' : ''}dark`}
|
className={`${className} btn${outline ? 'Outline' : ''}Primary`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ImCancelCircle } from 'react-icons/im';
|
|||||||
import { TbLock, TbLockOpen } from 'react-icons/tb';
|
import { TbLock, TbLockOpen } from 'react-icons/tb';
|
||||||
import { MdContentCopy, MdDone, MdOutlineArrowBack } from 'react-icons/md';
|
import { MdContentCopy, MdDone, MdOutlineArrowBack } from 'react-icons/md';
|
||||||
import { GrTest } from 'react-icons/gr';
|
import { GrTest } from 'react-icons/gr';
|
||||||
|
import { MdOutlineLibraryBooks } from "react-icons/md";
|
||||||
|
|
||||||
export const EditIcon = () => <FiEdit />;
|
export const EditIcon = () => <FiEdit />;
|
||||||
|
|
||||||
@@ -25,4 +26,6 @@ export const DoneIcon = () => <MdDone />;
|
|||||||
|
|
||||||
export const BackIcon = () => <MdOutlineArrowBack />;
|
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_vote_button_in_board?: boolean;
|
||||||
show_roadmap_in_header?: boolean;
|
show_roadmap_in_header?: boolean;
|
||||||
collapse_boards_in_header?: TenantSettingCollapseBoardsInHeader;
|
collapse_boards_in_header?: TenantSettingCollapseBoardsInHeader;
|
||||||
|
custom_css?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ITenantSetting;
|
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 siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
|
||||||
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
|
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
|
||||||
import siteSettingsAuthenticationReducer, { SiteSettingsAuthenticationState } from './SiteSettings/authenticationReducer';
|
import siteSettingsAuthenticationReducer, { SiteSettingsAuthenticationState } from './SiteSettings/authenticationReducer';
|
||||||
|
import siteSettingsAppearanceReducer, { SiteSettingsAppearanceState } from './SiteSettings/appearanceReducer';
|
||||||
|
|
||||||
interface SiteSettingsState {
|
interface SiteSettingsState {
|
||||||
general: SiteSettingsGeneralState;
|
general: SiteSettingsGeneralState;
|
||||||
@@ -102,6 +103,7 @@ interface SiteSettingsState {
|
|||||||
boards: SiteSettingsBoardsState;
|
boards: SiteSettingsBoardsState;
|
||||||
postStatuses: SiteSettingsPostStatusesState;
|
postStatuses: SiteSettingsPostStatusesState;
|
||||||
roadmap: SiteSettingsRoadmapState;
|
roadmap: SiteSettingsRoadmapState;
|
||||||
|
appearance: SiteSettingsAppearanceState;
|
||||||
users: SiteSettingsUsersState;
|
users: SiteSettingsUsersState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +113,7 @@ const initialState: SiteSettingsState = {
|
|||||||
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
||||||
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
||||||
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
||||||
|
appearance: siteSettingsAppearanceReducer(undefined, {} as any),
|
||||||
users: siteSettingsUsersReducer(undefined, {} as any),
|
users: siteSettingsUsersReducer(undefined, {} as any),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,6 +141,7 @@ const siteSettingsReducer = (
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
general: siteSettingsGeneralReducer(state.general, action),
|
general: siteSettingsGeneralReducer(state.general, action),
|
||||||
|
appearance: siteSettingsAppearanceReducer(state.general, action),
|
||||||
};
|
};
|
||||||
|
|
||||||
case OAUTH_SUBMIT_START:
|
case OAUTH_SUBMIT_START:
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ class TenantSetting < ApplicationRecord
|
|||||||
|
|
||||||
belongs_to :tenant
|
belongs_to :tenant
|
||||||
|
|
||||||
|
validates :custom_css, length: { maximum: 32000 }
|
||||||
|
|
||||||
enum brand_display: [
|
enum brand_display: [
|
||||||
:name_and_logo,
|
:name_and_logo,
|
||||||
:name_only,
|
:name_only,
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ class TenantSettingPolicy < ApplicationPolicy
|
|||||||
:show_vote_count,
|
:show_vote_count,
|
||||||
:show_vote_button_in_board,
|
:show_vote_button_in_board,
|
||||||
:show_roadmap_in_header,
|
:show_roadmap_in_header,
|
||||||
:collapse_boards_in_header
|
:collapse_boards_in_header,
|
||||||
|
:custom_css
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -25,5 +25,11 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<% if @tenant and not @tenant.tenant_setting.custom_css.blank? %>
|
||||||
|
<style type="text/css">
|
||||||
|
<%= @tenant.tenant_setting.custom_css %>
|
||||||
|
</style>
|
||||||
|
<% end %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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.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.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.roadmap'), path: site_settings_roadmap_path %>
|
||||||
|
<%= render 'menu_link', label: t('site_settings.menu.appearance'), path: site_settings_appearance_path %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render 'menu_link', label: t('site_settings.menu.users'), path: site_settings_users_path %>
|
<%= 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'
|
root_board_id: 'Root page'
|
||||||
show_roadmap_in_header: 'Show roadmap link in header'
|
show_roadmap_in_header: 'Show roadmap link in header'
|
||||||
collapse_boards_in_header: 'Collapse boards in header'
|
collapse_boards_in_header: 'Collapse boards in header'
|
||||||
|
custom_css: 'Custom CSS'
|
||||||
user:
|
user:
|
||||||
email: 'Email'
|
email: 'Email'
|
||||||
full_name: 'Full name'
|
full_name: 'Full name'
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ en:
|
|||||||
roadmap: 'Roadmap'
|
roadmap: 'Roadmap'
|
||||||
users: 'Users'
|
users: 'Users'
|
||||||
authentication: 'Authentication'
|
authentication: 'Authentication'
|
||||||
|
appearance: 'Appearance'
|
||||||
info_box:
|
info_box:
|
||||||
up_to_date: 'All changes saved'
|
up_to_date: 'All changes saved'
|
||||||
error: 'An error occurred: %{message}'
|
error: 'An error occurred: %{message}'
|
||||||
@@ -178,6 +179,9 @@ en:
|
|||||||
title2: 'Not in roadmap'
|
title2: 'Not in roadmap'
|
||||||
empty: 'The roadmap is empty.'
|
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.'
|
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:
|
users:
|
||||||
title: 'Users'
|
title: 'Users'
|
||||||
block: 'Block'
|
block: 'Block'
|
||||||
@@ -195,6 +199,7 @@ en:
|
|||||||
status_deleted: 'Deleted'
|
status_deleted: 'Deleted'
|
||||||
authentication:
|
authentication:
|
||||||
title: 'Authentication'
|
title: 'Authentication'
|
||||||
|
learn_more: 'Learn how to configure custom OAuth providers'
|
||||||
oauth_subtitle: 'OAuth providers'
|
oauth_subtitle: 'OAuth providers'
|
||||||
default_oauth: 'Default OAuth provider'
|
default_oauth: 'Default OAuth provider'
|
||||||
copy_url: 'Copy URL'
|
copy_url: 'Copy URL'
|
||||||
|
|||||||
@@ -50,11 +50,12 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :site_settings do
|
namespace :site_settings do
|
||||||
get 'general'
|
get 'general'
|
||||||
|
get 'authentication'
|
||||||
get 'boards'
|
get 'boards'
|
||||||
get 'post_statuses'
|
get 'post_statuses'
|
||||||
get 'roadmap'
|
get 'roadmap'
|
||||||
|
get 'appearance'
|
||||||
get 'users'
|
get 'users'
|
||||||
get 'authentication'
|
|
||||||
end
|
end
|
||||||
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
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.integer "root_board_id", default: 0, null: false
|
||||||
t.boolean "show_roadmap_in_header", default: true, null: false
|
t.boolean "show_roadmap_in_header", default: true, null: false
|
||||||
t.integer "collapse_boards_in_header", default: 0, 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"
|
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user