mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 11:17:49 +01:00
Add setting to manage visibility of vote count, vote button and decide root page (#197)
This commit is contained in:
committed by
GitHub
parent
d4242dd78e
commit
e7335f5622
@@ -1,14 +1,19 @@
|
|||||||
class StaticPagesController < ApplicationController
|
class StaticPagesController < ApplicationController
|
||||||
skip_before_action :load_tenant_data, only: [:showcase, :pending_tenant, :blocked_tenant]
|
skip_before_action :load_tenant_data, only: [:showcase, :pending_tenant, :blocked_tenant]
|
||||||
|
|
||||||
def roadmap
|
def root
|
||||||
@post_statuses = PostStatus
|
@board = Board.find_by(id: Current.tenant.tenant_setting.root_board_id)
|
||||||
.find_roadmap
|
|
||||||
.select(:id, :name, :color)
|
|
||||||
|
|
||||||
@posts = Post
|
if @board
|
||||||
.find_with_post_status_in(@post_statuses)
|
render 'boards/show'
|
||||||
.select(:id, :title, :board_id, :post_status_id, :user_id, :created_at)
|
else
|
||||||
|
get_roadmap_data
|
||||||
|
render 'static_pages/roadmap'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def roadmap
|
||||||
|
get_roadmap_data
|
||||||
end
|
end
|
||||||
|
|
||||||
def showcase
|
def showcase
|
||||||
@@ -20,4 +25,16 @@ class StaticPagesController < ApplicationController
|
|||||||
|
|
||||||
def blocked_tenant
|
def blocked_tenant
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_roadmap_data
|
||||||
|
@post_statuses = PostStatus
|
||||||
|
.find_roadmap
|
||||||
|
.select(:id, :name, :color)
|
||||||
|
|
||||||
|
@posts = Post
|
||||||
|
.find_with_post_status_in(@post_statuses)
|
||||||
|
.select(:id, :title, :board_id, :post_status_id, :user_id, :created_at)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -76,8 +76,6 @@ export const updateTenant = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(body)
|
|
||||||
|
|
||||||
const res = await fetch(`/tenants/0`, {
|
const res = await fetch(`/tenants/0`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: buildRequestHeaders(authenticityToken),
|
headers: buildRequestHeaders(authenticityToken),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import PostList from './PostList';
|
|||||||
import Sidebar from '../common/Sidebar';
|
import Sidebar from '../common/Sidebar';
|
||||||
|
|
||||||
import IBoard from '../../interfaces/IBoard';
|
import IBoard from '../../interfaces/IBoard';
|
||||||
|
import ITenantSetting from '../../interfaces/ITenantSetting';
|
||||||
|
|
||||||
import { PostsState } from '../../reducers/postsReducer';
|
import { PostsState } from '../../reducers/postsReducer';
|
||||||
import { PostStatusesState } from '../../reducers/postStatusesReducer';
|
import { PostStatusesState } from '../../reducers/postStatusesReducer';
|
||||||
@@ -14,6 +15,8 @@ import { PostStatusesState } from '../../reducers/postStatusesReducer';
|
|||||||
interface Props {
|
interface Props {
|
||||||
board: IBoard;
|
board: IBoard;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
|
isPowerUser: boolean;
|
||||||
|
tenantSetting: ITenantSetting;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
posts: PostsState;
|
posts: PostsState;
|
||||||
postStatuses: PostStatusesState;
|
postStatuses: PostStatusesState;
|
||||||
@@ -63,6 +66,8 @@ class BoardP extends React.Component<Props> {
|
|||||||
const {
|
const {
|
||||||
board,
|
board,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
|
isPowerUser,
|
||||||
|
tenantSetting,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
posts,
|
posts,
|
||||||
postStatuses,
|
postStatuses,
|
||||||
@@ -97,6 +102,8 @@ class BoardP extends React.Component<Props> {
|
|||||||
|
|
||||||
<PostList
|
<PostList
|
||||||
posts={posts.items}
|
posts={posts.items}
|
||||||
|
showLikeCount={isPowerUser || tenantSetting.show_vote_count}
|
||||||
|
showLikeButtons={tenantSetting.show_vote_button_in_board}
|
||||||
postStatuses={postStatuses.items}
|
postStatuses={postStatuses.items}
|
||||||
areLoading={posts.areLoading}
|
areLoading={posts.areLoading}
|
||||||
error={posts.error}
|
error={posts.error}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import IPostStatus from '../../interfaces/IPostStatus';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
posts: Array<IPost>;
|
posts: Array<IPost>;
|
||||||
|
showLikeCount: boolean;
|
||||||
|
showLikeButtons: boolean;
|
||||||
postStatuses: Array<IPostStatus>;
|
postStatuses: Array<IPostStatus>;
|
||||||
areLoading: boolean;
|
areLoading: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
@@ -27,6 +29,8 @@ interface Props {
|
|||||||
|
|
||||||
const PostList = ({
|
const PostList = ({
|
||||||
posts,
|
posts,
|
||||||
|
showLikeCount,
|
||||||
|
showLikeButtons,
|
||||||
postStatuses,
|
postStatuses,
|
||||||
areLoading,
|
areLoading,
|
||||||
error,
|
error,
|
||||||
@@ -53,7 +57,9 @@ const PostList = ({
|
|||||||
title={post.title}
|
title={post.title}
|
||||||
description={post.description}
|
description={post.description}
|
||||||
postStatus={postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
postStatus={postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
|
||||||
likesCount={post.likesCount}
|
likeCount={post.likeCount}
|
||||||
|
showLikeCount={showLikeCount}
|
||||||
|
showLikeButtons={showLikeButtons}
|
||||||
liked={post.liked}
|
liked={post.liked}
|
||||||
commentsCount={post.commentsCount}
|
commentsCount={post.commentsCount}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ interface Props {
|
|||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
postStatus: IPostStatus;
|
postStatus: IPostStatus;
|
||||||
likesCount: number;
|
likeCount: number;
|
||||||
|
showLikeCount: boolean;
|
||||||
|
showLikeButtons: boolean;
|
||||||
liked: number;
|
liked: number;
|
||||||
commentsCount: number;
|
commentsCount: number;
|
||||||
|
|
||||||
@@ -25,7 +27,9 @@ const PostListItem = ({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
postStatus,
|
postStatus,
|
||||||
likesCount,
|
likeCount,
|
||||||
|
showLikeCount,
|
||||||
|
showLikeButtons,
|
||||||
liked,
|
liked,
|
||||||
commentsCount,
|
commentsCount,
|
||||||
|
|
||||||
@@ -35,7 +39,9 @@ const PostListItem = ({
|
|||||||
<div onClick={() => window.location.href = `/posts/${id}`} className="postListItem">
|
<div onClick={() => window.location.href = `/posts/${id}`} className="postListItem">
|
||||||
<LikeButton
|
<LikeButton
|
||||||
postId={id}
|
postId={id}
|
||||||
likesCount={likesCount}
|
likeCount={likeCount}
|
||||||
|
showLikeCount={showLikeCount}
|
||||||
|
showLikeButton={showLikeButtons}
|
||||||
liked={liked}
|
liked={liked}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ import IBoard from '../../interfaces/IBoard';
|
|||||||
|
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
import { State } from '../../reducers/rootReducer';
|
import { State } from '../../reducers/rootReducer';
|
||||||
|
import ITenantSetting from '../../interfaces/ITenantSetting';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
board: IBoard;
|
board: IBoard;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
|
isPowerUser: boolean;
|
||||||
|
tenantSetting: ITenantSetting;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,13 +28,21 @@ class BoardRoot extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { board, isLoggedIn, authenticityToken } = this.props;
|
const {
|
||||||
|
board,
|
||||||
|
isLoggedIn,
|
||||||
|
isPowerUser,
|
||||||
|
tenantSetting,
|
||||||
|
authenticityToken,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={this.store}>
|
<Provider store={this.store}>
|
||||||
<Board
|
<Board
|
||||||
board={board}
|
board={board}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
|
isPowerUser={isPowerUser}
|
||||||
|
tenantSetting={tenantSetting}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import * as React from 'react';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postId: number;
|
postId: number;
|
||||||
likesCount: number;
|
likeCount: number;
|
||||||
|
showLikeCount?: boolean;
|
||||||
|
showLikeButton?: boolean;
|
||||||
liked: number;
|
liked: number;
|
||||||
handleLikeSubmit(
|
handleLikeSubmit(
|
||||||
postId: number,
|
postId: number,
|
||||||
@@ -15,7 +17,9 @@ interface Props {
|
|||||||
|
|
||||||
const LikeButtonP = ({
|
const LikeButtonP = ({
|
||||||
postId,
|
postId,
|
||||||
likesCount,
|
likeCount,
|
||||||
|
showLikeCount = true,
|
||||||
|
showLikeButton = true,
|
||||||
liked,
|
liked,
|
||||||
handleLikeSubmit,
|
handleLikeSubmit,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
@@ -29,9 +33,10 @@ const LikeButtonP = ({
|
|||||||
else window.location.href = `/users/sign_in`;
|
else window.location.href = `/users/sign_in`;
|
||||||
}}
|
}}
|
||||||
className={`likeButton${liked ? ' liked' : ''}`}
|
className={`likeButton${liked ? ' liked' : ''}`}
|
||||||
|
hidden={!showLikeButton}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span className="likesCountLabel">{likesCount}</span>
|
{ showLikeCount && <span className="likeCountLabel">{likeCount}</span> }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { getLabel } from '../../helpers/formUtils';
|
||||||
|
|
||||||
import IBoard from '../../interfaces/IBoard';
|
import IBoard from '../../interfaces/IBoard';
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ const PostBoardSelect = ({
|
|||||||
id="selectPickerBoard"
|
id="selectPickerBoard"
|
||||||
className="selectPicker"
|
className="selectPicker"
|
||||||
>
|
>
|
||||||
<optgroup label="Boards">
|
<optgroup label={getLabel('board')}>
|
||||||
{boards.map((board, i) => (
|
{boards.map((board, i) => (
|
||||||
<option value={board.id} key={i}>
|
<option value={board.id} key={i}>
|
||||||
{board.name}
|
{board.name}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import IPost from '../../interfaces/IPost';
|
import IPost from '../../interfaces/IPost';
|
||||||
import IPostStatus from '../../interfaces/IPostStatus';
|
import IPostStatus from '../../interfaces/IPostStatus';
|
||||||
import IBoard from '../../interfaces/IBoard';
|
import IBoard from '../../interfaces/IBoard';
|
||||||
|
import ITenantSetting from '../../interfaces/ITenantSetting';
|
||||||
|
|
||||||
import PostUpdateList from './PostUpdateList';
|
import PostUpdateList from './PostUpdateList';
|
||||||
import PostEditForm from './PostEditForm';
|
import PostEditForm from './PostEditForm';
|
||||||
@@ -39,6 +40,7 @@ interface Props {
|
|||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
currentUserFullName: string;
|
currentUserFullName: string;
|
||||||
currentUserEmail: string;
|
currentUserEmail: string;
|
||||||
|
tenantSetting: ITenantSetting;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
|
|
||||||
requestPost(postId: number): void;
|
requestPost(postId: number): void;
|
||||||
@@ -148,6 +150,7 @@ class PostP extends React.Component<Props> {
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isPowerUser,
|
isPowerUser,
|
||||||
currentUserEmail,
|
currentUserEmail,
|
||||||
|
tenantSetting,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
|
|
||||||
submitFollow,
|
submitFollow,
|
||||||
@@ -176,11 +179,14 @@ class PostP extends React.Component<Props> {
|
|||||||
error={comments.error}
|
error={comments.error}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LikeList
|
{
|
||||||
likes={likes.items}
|
isPowerUser &&
|
||||||
areLoading={likes.areLoading}
|
<LikeList
|
||||||
error={likes.error}
|
likes={likes.items}
|
||||||
/>
|
areLoading={likes.areLoading}
|
||||||
|
error={likes.error}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<ActionBox
|
<ActionBox
|
||||||
followed={followed}
|
followed={followed}
|
||||||
@@ -213,7 +219,8 @@ class PostP extends React.Component<Props> {
|
|||||||
<div className="postHeader">
|
<div className="postHeader">
|
||||||
<LikeButton
|
<LikeButton
|
||||||
postId={post.id}
|
postId={post.id}
|
||||||
likesCount={likes.items.length}
|
likeCount={likes.items.length}
|
||||||
|
showLikeCount={isPowerUser || tenantSetting.show_vote_count}
|
||||||
liked={likes.items.find(like => like.email === currentUserEmail) ? 1 : 0}
|
liked={likes.items.find(like => like.email === currentUserEmail) ? 1 : 0}
|
||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|||||||
import I18n from 'i18n-js';
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import IPostStatus from '../../interfaces/IPostStatus';
|
import IPostStatus from '../../interfaces/IPostStatus';
|
||||||
|
import { getLabel } from '../../helpers/formUtils';
|
||||||
|
|
||||||
const NO_POST_STATUS_VALUE = 'none';
|
const NO_POST_STATUS_VALUE = 'none';
|
||||||
|
|
||||||
@@ -28,16 +29,16 @@ const PostStatusSelect = ({
|
|||||||
id="selectPickerStatus"
|
id="selectPickerStatus"
|
||||||
className="selectPicker"
|
className="selectPicker"
|
||||||
>
|
>
|
||||||
<optgroup label="Post statuses">
|
<optgroup label={getLabel('post_status')}>
|
||||||
{postStatuses.map((postStatus, i) => (
|
{postStatuses.map((postStatus, i) => (
|
||||||
<option value={postStatus.id} key={i}>
|
<option value={postStatus.id} key={i}>
|
||||||
{postStatus.name}
|
{postStatus.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="No post status">
|
<option value={NO_POST_STATUS_VALUE}>
|
||||||
<option value={NO_POST_STATUS_VALUE}>{I18n.t('post.post_status_select.no_post_status')}</option>
|
{I18n.t('post.post_status_select.no_post_status')}
|
||||||
</optgroup>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import IPostStatus from '../../interfaces/IPostStatus';
|
|||||||
|
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
import { State } from '../../reducers/rootReducer';
|
import { State } from '../../reducers/rootReducer';
|
||||||
|
import ITenantSetting from '../../interfaces/ITenantSetting';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postId: number;
|
postId: number;
|
||||||
@@ -19,6 +20,7 @@ interface Props {
|
|||||||
isPowerUser: boolean;
|
isPowerUser: boolean;
|
||||||
currentUserFullName: string;
|
currentUserFullName: string;
|
||||||
currentUserEmail: string;
|
currentUserEmail: string;
|
||||||
|
tenantSetting: ITenantSetting;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ class PostRoot extends React.Component<Props> {
|
|||||||
isPowerUser,
|
isPowerUser,
|
||||||
currentUserFullName,
|
currentUserFullName,
|
||||||
currentUserEmail,
|
currentUserEmail,
|
||||||
|
tenantSetting,
|
||||||
authenticityToken
|
authenticityToken
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ class PostRoot extends React.Component<Props> {
|
|||||||
isPowerUser={isPowerUser}
|
isPowerUser={isPowerUser}
|
||||||
currentUserFullName={currentUserFullName}
|
currentUserFullName={currentUserFullName}
|
||||||
currentUserEmail={currentUserEmail}
|
currentUserEmail={currentUserEmail}
|
||||||
|
tenantSetting={tenantSetting}
|
||||||
authenticityToken={authenticityToken}
|
authenticityToken={authenticityToken}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -12,18 +12,23 @@ import {
|
|||||||
TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY,
|
TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY,
|
||||||
TENANT_SETTING_BRAND_DISPLAY_NONE,
|
TENANT_SETTING_BRAND_DISPLAY_NONE,
|
||||||
} from '../../../interfaces/ITenantSetting';
|
} from '../../../interfaces/ITenantSetting';
|
||||||
import { DangerText } from '../../common/CustomTexts';
|
import { DangerText, SmallMutedText } from '../../common/CustomTexts';
|
||||||
import { getLabel, getValidationMessage } from '../../../helpers/formUtils';
|
import { getLabel, getValidationMessage } from '../../../helpers/formUtils';
|
||||||
|
import IBoardJSON from '../../../interfaces/json/IBoard';
|
||||||
|
|
||||||
export interface ISiteSettingsGeneralForm {
|
export interface ISiteSettingsGeneralForm {
|
||||||
siteName: string;
|
siteName: string;
|
||||||
siteLogo: string;
|
siteLogo: string;
|
||||||
brandDisplaySetting: string;
|
brandDisplaySetting: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
|
showVoteCount: boolean;
|
||||||
|
showVoteButtonInBoard: boolean;
|
||||||
|
rootBoardId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
originForm: ISiteSettingsGeneralForm;
|
originForm: ISiteSettingsGeneralForm;
|
||||||
|
boards: IBoardJSON[];
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
|
|
||||||
areUpdating: boolean;
|
areUpdating: boolean;
|
||||||
@@ -34,12 +39,16 @@ interface Props {
|
|||||||
siteLogo: string,
|
siteLogo: string,
|
||||||
brandDisplaySetting: string,
|
brandDisplaySetting: string,
|
||||||
locale: string,
|
locale: string,
|
||||||
|
rootBoardId: number,
|
||||||
|
showVoteCount: boolean,
|
||||||
|
showVoteButtonInBoard: boolean,
|
||||||
authenticityToken: string
|
authenticityToken: string
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeneralSiteSettingsP = ({
|
const GeneralSiteSettingsP = ({
|
||||||
originForm,
|
originForm,
|
||||||
|
boards,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
|
|
||||||
areUpdating,
|
areUpdating,
|
||||||
@@ -56,6 +65,9 @@ const GeneralSiteSettingsP = ({
|
|||||||
siteLogo: originForm.siteLogo,
|
siteLogo: originForm.siteLogo,
|
||||||
brandDisplaySetting: originForm.brandDisplaySetting,
|
brandDisplaySetting: originForm.brandDisplaySetting,
|
||||||
locale: originForm.locale,
|
locale: originForm.locale,
|
||||||
|
showVoteCount: originForm.showVoteCount,
|
||||||
|
showVoteButtonInBoard: originForm.showVoteButtonInBoard,
|
||||||
|
rootBoardId: originForm.rootBoardId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,6 +77,9 @@ const GeneralSiteSettingsP = ({
|
|||||||
data.siteLogo,
|
data.siteLogo,
|
||||||
data.brandDisplaySetting,
|
data.brandDisplaySetting,
|
||||||
data.locale,
|
data.locale,
|
||||||
|
Number(data.rootBoardId),
|
||||||
|
data.showVoteCount,
|
||||||
|
data.showVoteButtonInBoard,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
).then(res => {
|
).then(res => {
|
||||||
if (res?.status !== HttpStatus.OK) return;
|
if (res?.status !== HttpStatus.OK) return;
|
||||||
@@ -99,7 +114,7 @@ const GeneralSiteSettingsP = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="formGroup col-4">
|
<div className="formGroup col-4">
|
||||||
<label htmlFor="brandSetting">{ getLabel('tenant', 'brand_setting') }</label>
|
<label htmlFor="brandSetting">{ getLabel('tenant_setting', 'brand_display') }</label>
|
||||||
<select
|
<select
|
||||||
{...register('brandDisplaySetting')}
|
{...register('brandDisplaySetting')}
|
||||||
id="brandSetting"
|
id="brandSetting"
|
||||||
@@ -136,6 +151,48 @@ const GeneralSiteSettingsP = ({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="formGroup">
|
||||||
|
<label htmlFor="rootBoardId">{ getLabel('tenant_setting', 'root_board_id') }</label>
|
||||||
|
<select
|
||||||
|
{...register('rootBoardId')}
|
||||||
|
id="rootBoardId"
|
||||||
|
className="selectPicker"
|
||||||
|
>
|
||||||
|
<option value="0">
|
||||||
|
{I18n.t('roadmap.title')}
|
||||||
|
</option>
|
||||||
|
<optgroup label={getLabel('board')}>
|
||||||
|
{boards.map((board, i) => (
|
||||||
|
<option value={board.id} key={i}>{board.name}</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div className="formGroup">
|
||||||
|
<div className="checkboxSwitch">
|
||||||
|
<input {...register('showVoteCount')} type="checkbox" id="show_vote_count_checkbox" />
|
||||||
|
<label htmlFor="show_vote_count_checkbox">{ getLabel('tenant_setting', 'show_vote_count') }</label>
|
||||||
|
<SmallMutedText>
|
||||||
|
{ I18n.t('site_settings.general.show_vote_count_help') }
|
||||||
|
</SmallMutedText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div className="formGroup">
|
||||||
|
<div className="checkboxSwitch">
|
||||||
|
<input {...register('showVoteButtonInBoard')} type="checkbox" id="show_vote_button_in_board_checkbox" />
|
||||||
|
<label htmlFor="show_vote_button_in_board_checkbox">{ getLabel('tenant_setting', 'show_vote_button_in_board') }</label>
|
||||||
|
<SmallMutedText>
|
||||||
|
{ I18n.t('site_settings.general.show_vote_button_in_board_help') }
|
||||||
|
</SmallMutedText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Button onClick={() => null} disabled={!isDirty}>
|
<Button onClick={() => null} disabled={!isDirty}>
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { Store } from 'redux';
|
|||||||
|
|
||||||
import GeneralSiteSettings from '../../../containers/GeneralSiteSettings';
|
import GeneralSiteSettings from '../../../containers/GeneralSiteSettings';
|
||||||
import createStoreHelper from '../../../helpers/createStore';
|
import createStoreHelper from '../../../helpers/createStore';
|
||||||
|
import IBoardJSON from '../../../interfaces/json/IBoard';
|
||||||
import { State } from '../../../reducers/rootReducer';
|
import { State } from '../../../reducers/rootReducer';
|
||||||
import { ISiteSettingsGeneralForm } from './GeneralSiteSettingsP';
|
import { ISiteSettingsGeneralForm } from './GeneralSiteSettingsP';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
originForm: ISiteSettingsGeneralForm;
|
originForm: ISiteSettingsGeneralForm;
|
||||||
|
boards: IBoardJSON[];
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ class GeneralSiteSettingsRoot extends React.Component<Props> {
|
|||||||
<Provider store={this.store}>
|
<Provider store={this.store}>
|
||||||
<GeneralSiteSettings
|
<GeneralSiteSettings
|
||||||
originForm={this.props.originForm}
|
originForm={this.props.originForm}
|
||||||
|
boards={this.props.boards}
|
||||||
authenticityToken={this.props.authenticityToken}
|
authenticityToken={this.props.authenticityToken}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import I18n from 'i18n-js';
|
|||||||
|
|
||||||
import Button from '../../common/Button';
|
import Button from '../../common/Button';
|
||||||
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_MODERATOR, USER_ROLE_USER } from '../../../interfaces/IUser';
|
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_MODERATOR, USER_ROLE_USER } from '../../../interfaces/IUser';
|
||||||
|
import { getLabel } from '../../../helpers/formUtils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
@@ -57,7 +58,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
id="selectPickerUserRole"
|
id="selectPickerUserRole"
|
||||||
className="selectPicker"
|
className="selectPicker"
|
||||||
>
|
>
|
||||||
<optgroup label="Roles">
|
<optgroup label={getLabel('user', 'role')}>
|
||||||
<option value={USER_ROLE_USER}>
|
<option value={USER_ROLE_USER}>
|
||||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_USER}`) }
|
{ I18n.t(`site_settings.users.role_${USER_ROLE_USER}`) }
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -16,12 +16,20 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
siteLogo: string,
|
siteLogo: string,
|
||||||
brandDisplaySetting: TenantSettingBrandDisplay,
|
brandDisplaySetting: TenantSettingBrandDisplay,
|
||||||
locale: string,
|
locale: string,
|
||||||
|
rootBoardId: number,
|
||||||
|
showVoteCount: boolean,
|
||||||
|
showVoteButtonInBoard: boolean,
|
||||||
authenticityToken: string
|
authenticityToken: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return dispatch(updateTenant({
|
return dispatch(updateTenant({
|
||||||
siteName,
|
siteName,
|
||||||
siteLogo,
|
siteLogo,
|
||||||
tenantSetting: { brand_display: brandDisplaySetting },
|
tenantSetting: {
|
||||||
|
brand_display: brandDisplaySetting,
|
||||||
|
show_vote_count: showVoteCount,
|
||||||
|
show_vote_button_in_board: showVoteButtonInBoard,
|
||||||
|
root_board_id: rootBoardId,
|
||||||
|
},
|
||||||
locale,
|
locale,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import I18n from 'i18n-js';
|
|||||||
|
|
||||||
export const getLabel = (
|
export const getLabel = (
|
||||||
entity: string,
|
entity: string,
|
||||||
attribute: string,
|
attribute: string = undefined,
|
||||||
) => (
|
) => (
|
||||||
I18n.t(`activerecord.attributes.${entity}.${attribute}`)
|
attribute ?
|
||||||
|
I18n.t(`activerecord.attributes.${entity}.${attribute}`)
|
||||||
|
:
|
||||||
|
I18n.t(`activerecord.models.${entity}.one`)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getValidationMessage = (
|
export const getValidationMessage = (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ interface IPost {
|
|||||||
description?: string;
|
description?: string;
|
||||||
boardId: number;
|
boardId: number;
|
||||||
postStatusId?: number;
|
postStatusId?: number;
|
||||||
likesCount: number;
|
likeCount: number;
|
||||||
liked: number;
|
liked: number;
|
||||||
commentsCount: number;
|
commentsCount: number;
|
||||||
hotness: number;
|
hotness: number;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export type TenantSettingBrandDisplay =
|
|||||||
|
|
||||||
interface ITenantSetting {
|
interface ITenantSetting {
|
||||||
brand_display?: TenantSettingBrandDisplay;
|
brand_display?: TenantSettingBrandDisplay;
|
||||||
|
root_board_id?: number;
|
||||||
|
show_vote_count?: boolean;
|
||||||
|
show_vote_button_in_board?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ITenantSetting;
|
export default ITenantSetting;
|
||||||
@@ -16,7 +16,7 @@ const initialState: IPost = {
|
|||||||
description: null,
|
description: null,
|
||||||
boardId: 0,
|
boardId: 0,
|
||||||
postStatusId: null,
|
postStatusId: null,
|
||||||
likesCount: 0,
|
likeCount: 0,
|
||||||
liked: 0,
|
liked: 0,
|
||||||
commentsCount: 0,
|
commentsCount: 0,
|
||||||
hotness: 0,
|
hotness: 0,
|
||||||
@@ -40,7 +40,7 @@ const postReducer = (
|
|||||||
description: action.post.description,
|
description: action.post.description,
|
||||||
boardId: action.post.board_id,
|
boardId: action.post.board_id,
|
||||||
postStatusId: action.post.post_status_id,
|
postStatusId: action.post.post_status_id,
|
||||||
likesCount: action.post.likes_count,
|
likeCount: action.post.likes_count,
|
||||||
liked: action.post.liked,
|
liked: action.post.liked,
|
||||||
commentsCount: action.post.comments_count,
|
commentsCount: action.post.comments_count,
|
||||||
hotness: action.post.hotness,
|
hotness: action.post.hotness,
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ const postsReducer = (
|
|||||||
items: state.items.map(post => {
|
items: state.items.map(post => {
|
||||||
if (action.postId === post.id) {
|
if (action.postId === post.id) {
|
||||||
return action.isLike ?
|
return action.isLike ?
|
||||||
{ ...post, likesCount: post.likesCount + 1, liked: 1 }
|
{ ...post, likeCount: post.likeCount + 1, liked: 1 }
|
||||||
:
|
:
|
||||||
{ ...post, likesCount: post.likesCount - 1, liked: 0 }
|
{ ...post, likeCount: post.likeCount - 1, liked: 0 }
|
||||||
} else return post;
|
} else return post;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
border-bottom-color: $primary-color;
|
border-bottom-color: $primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.likesCountLabel {
|
.likeCountLabel {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class TenantSettingPolicy < ApplicationPolicy
|
class TenantSettingPolicy < ApplicationPolicy
|
||||||
def permitted_attributes_for_update
|
def permitted_attributes_for_update
|
||||||
if user.admin?
|
if user.admin?
|
||||||
[:brand_display]
|
[:brand_display, :root_board_id, :show_vote_count, :show_vote_button_in_board]
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
{
|
{
|
||||||
board: @board,
|
board: @board,
|
||||||
isLoggedIn: user_signed_in?,
|
isLoggedIn: user_signed_in?,
|
||||||
authenticityToken: form_authenticity_token,
|
isPowerUser: user_signed_in? ? current_user.moderator? : false,
|
||||||
|
tenantSetting: @tenant_setting,
|
||||||
|
authenticityToken: form_authenticity_token
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
%>
|
%>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
isPowerUser: user_signed_in? ? current_user.moderator? : false,
|
isPowerUser: user_signed_in? ? current_user.moderator? : false,
|
||||||
currentUserFullName: user_signed_in? ? current_user.full_name : nil,
|
currentUserFullName: user_signed_in? ? current_user.full_name : nil,
|
||||||
currentUserEmail: user_signed_in? ? current_user.email : nil,
|
currentUserEmail: user_signed_in? ? current_user.email : nil,
|
||||||
|
tenantSetting: @tenant_setting,
|
||||||
authenticityToken: form_authenticity_token,
|
authenticityToken: form_authenticity_token,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,8 +9,12 @@
|
|||||||
siteName: @tenant.site_name,
|
siteName: @tenant.site_name,
|
||||||
siteLogo: @tenant.site_logo,
|
siteLogo: @tenant.site_logo,
|
||||||
brandDisplaySetting: @tenant_setting.brand_display,
|
brandDisplaySetting: @tenant_setting.brand_display,
|
||||||
|
showVoteCount: @tenant_setting.show_vote_count,
|
||||||
|
showVoteButtonInBoard: @tenant_setting.show_vote_button_in_board,
|
||||||
|
rootBoardId: @tenant_setting.root_board_id.to_s,
|
||||||
locale: @tenant.locale
|
locale: @tenant.locale
|
||||||
},
|
},
|
||||||
|
boards: @tenant.boards.order(order: :asc),
|
||||||
authenticityToken: form_authenticity_token
|
authenticityToken: form_authenticity_token
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,6 +47,28 @@ en:
|
|||||||
subject: '[%{app_name}] Status change on post %{post}'
|
subject: '[%{app_name}] Status change on post %{post}'
|
||||||
body: "The post you're following %{post} has a new status"
|
body: "The post you're following %{post} has a new status"
|
||||||
activerecord:
|
activerecord:
|
||||||
|
models:
|
||||||
|
board:
|
||||||
|
one: 'Board'
|
||||||
|
other: 'Boards'
|
||||||
|
comment:
|
||||||
|
one: 'Comment'
|
||||||
|
other: 'Comments'
|
||||||
|
like:
|
||||||
|
one: 'Vote'
|
||||||
|
other: 'Votes'
|
||||||
|
o_auth:
|
||||||
|
one: 'OAuth'
|
||||||
|
other: 'OAuths'
|
||||||
|
post_status:
|
||||||
|
one: 'Status'
|
||||||
|
other: 'Statuses'
|
||||||
|
post:
|
||||||
|
one: 'Post'
|
||||||
|
other: 'Posts'
|
||||||
|
user:
|
||||||
|
one: 'User'
|
||||||
|
other: 'Users'
|
||||||
attributes:
|
attributes:
|
||||||
board:
|
board:
|
||||||
name: 'Name'
|
name: 'Name'
|
||||||
@@ -92,7 +114,11 @@ en:
|
|||||||
site_logo: 'Site logo'
|
site_logo: 'Site logo'
|
||||||
subdomain: 'Subdomain'
|
subdomain: 'Subdomain'
|
||||||
locale: 'Language'
|
locale: 'Language'
|
||||||
brand_setting: 'Display'
|
tenant_setting:
|
||||||
|
brand_display: 'Display'
|
||||||
|
show_vote_count: 'Show vote count to users'
|
||||||
|
show_vote_button_in_board: 'Show vote buttons in board page'
|
||||||
|
root_board_id: 'Root page'
|
||||||
user:
|
user:
|
||||||
email: 'Email'
|
email: 'Email'
|
||||||
full_name: 'Full name'
|
full_name: 'Full name'
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ en:
|
|||||||
brand_setting_name: 'Name only'
|
brand_setting_name: 'Name only'
|
||||||
brand_setting_logo: 'Logo only'
|
brand_setting_logo: 'Logo only'
|
||||||
brand_setting_none: 'None'
|
brand_setting_none: 'None'
|
||||||
|
show_vote_count_help: 'If you enable this setting, users will be able to see the vote count of posts. This may incentivize users to vote on already popular posts, leading to a snowball effect.'
|
||||||
|
show_vote_button_in_board_help: 'If you enable this setting, users will be able to vote posts from the board page. This may incentivize users to vote on more posts, leading to a higher number of votes but of lower significance.'
|
||||||
boards:
|
boards:
|
||||||
title: 'Boards'
|
title: 'Boards'
|
||||||
empty: 'There are no boards. Create one below!'
|
empty: 'There are no boards. Create one below!'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
constraints subdomain: /.*/ do
|
constraints subdomain: /.*/ do
|
||||||
root to: 'static_pages#roadmap'
|
root to: 'static_pages#root'
|
||||||
|
|
||||||
get '/roadmap', to: 'static_pages#roadmap'
|
get '/roadmap', to: 'static_pages#roadmap'
|
||||||
get '/pending-tenant', to: 'static_pages#pending_tenant'
|
get '/pending-tenant', to: 'static_pages#pending_tenant'
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class AddShowVoteAndButtonVoteToTenantSetting < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :tenant_settings, :show_vote_count, :boolean, null: false, default: false
|
||||||
|
add_column :tenant_settings, :show_vote_button_in_board, :boolean, null: false, default: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddRootBoardIdToTenantSetting < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :tenant_settings, :root_board_id, :integer, null: false, default: 0
|
||||||
|
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: 2023_01_31_194858) do
|
ActiveRecord::Schema.define(version: 2023_02_04_171748) 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"
|
||||||
@@ -129,6 +129,9 @@ ActiveRecord::Schema.define(version: 2023_01_31_194858) do
|
|||||||
t.bigint "tenant_id", null: false
|
t.bigint "tenant_id", null: false
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", precision: 6, null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.boolean "show_vote_count", default: false, null: false
|
||||||
|
t.boolean "show_vote_button_in_board", default: false, null: false
|
||||||
|
t.integer "root_board_id", default: 0, null: false
|
||||||
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :tenant_setting do
|
factory :tenant_setting do
|
||||||
brand_display { 0 }
|
|
||||||
tenant
|
tenant
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,4 +19,16 @@ RSpec.describe TenantSetting, type: :model do
|
|||||||
tenant_setting.brand_display = 'no_name_no_logo'
|
tenant_setting.brand_display = 'no_name_no_logo'
|
||||||
expect(tenant_setting).to be_valid
|
expect(tenant_setting).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'has a setting to show vote count' do
|
||||||
|
expect(tenant_setting.show_vote_count).to be_falsy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a setting to show vote button in board view' do
|
||||||
|
expect(tenant_setting.show_vote_button_in_board).to be_falsy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a setting that contains the board id of the root page' do
|
||||||
|
expect(tenant_setting.root_board_id).to eq(0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ require 'rails_helper'
|
|||||||
RSpec.describe 'static pages routing', :aggregate_failures, type: :routing do
|
RSpec.describe 'static pages routing', :aggregate_failures, type: :routing do
|
||||||
it 'routes roadmap' do
|
it 'routes roadmap' do
|
||||||
expect(get: '/').to route_to(
|
expect(get: '/').to route_to(
|
||||||
controller: 'static_pages', action: 'roadmap'
|
controller: 'static_pages', action: 'root'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(get: '/roadmap').to route_to(
|
expect(get: '/roadmap').to route_to(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ feature 'likes', type: :system, js: true do
|
|||||||
let(:post_header_selector) { '.postHeader' }
|
let(:post_header_selector) { '.postHeader' }
|
||||||
let(:like_button_container_selector) { '.likeButtonContainer' }
|
let(:like_button_container_selector) { '.likeButtonContainer' }
|
||||||
let(:like_button_selector) { '.likeButton' }
|
let(:like_button_selector) { '.likeButton' }
|
||||||
let(:likes_count_label_selector) { '.likesCountLabel' }
|
let(:likes_count_label_selector) { '.likeCountLabel' }
|
||||||
let(:like_list_container_selector) { '.likeListContainer' }
|
let(:like_list_container_selector) { '.likeListContainer' }
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
|
|||||||
Reference in New Issue
Block a user