Add Site settings > General (#133)

This commit is contained in:
Riccardo Graziosi
2022-07-18 10:47:54 +02:00
committed by GitHub
parent bdc4004e4a
commit 35831b9801
99 changed files with 2405 additions and 281 deletions

View File

@@ -0,0 +1,59 @@
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import ITenantJSON from '../../interfaces/json/ITenant';
import { State } from '../../reducers/rootReducer';
export const TENANT_REQUEST_START = 'TENANT_REQUEST_START';
interface TenantRequestStartAction {
type: typeof TENANT_REQUEST_START;
}
export const TENANT_REQUEST_SUCCESS = 'TENANT_REQUEST_SUCCESS';
interface TenantRequestSuccessAction {
type: typeof TENANT_REQUEST_SUCCESS;
tenant: ITenantJSON;
}
export const TENANT_REQUEST_FAILURE = 'TENANT_REQUEST_FAILURE';
interface TenantRequestFailureAction {
type: typeof TENANT_REQUEST_FAILURE;
error: string;
}
export type TenantRequestActionTypes =
TenantRequestStartAction |
TenantRequestSuccessAction |
TenantRequestFailureAction;
const tenantRequestStart = (): TenantRequestActionTypes => ({
type: TENANT_REQUEST_START,
});
const tenantRequestSuccess = (
tenant: ITenantJSON
): TenantRequestActionTypes => ({
type: TENANT_REQUEST_SUCCESS,
tenant,
});
const tenantRequestFailure = (error: string): TenantRequestActionTypes => ({
type: TENANT_REQUEST_FAILURE,
error,
});
export const requestTenant = (): ThunkAction<void, State, null, Action<string>> => (
async (dispatch) => {
dispatch(tenantRequestStart());
try {
const response = await fetch('/tenants/0');
const json = await response.json();
dispatch(tenantRequestSuccess(json));
} catch (e) {
dispatch(tenantRequestFailure(e));
}
}
);

View File

@@ -0,0 +1,82 @@
import { Action } from "redux";
import { ThunkAction } from "redux-thunk";
import HttpStatus from "../../constants/http_status";
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
import ITenantJSON from "../../interfaces/json/ITenant";
import { State } from "../../reducers/rootReducer";
export const TENANT_SUBMIT_START = 'TENANT_SUBMIT_START';
interface TenantSubmitStartAction {
type: typeof TENANT_SUBMIT_START;
}
export const TENANT_SUBMIT_SUCCESS = 'TENANT_SUBMIT_SUCCESS';
interface TenantSubmitSuccessAction {
type: typeof TENANT_SUBMIT_SUCCESS;
tenant: ITenantJSON;
}
export const TENANT_SUBMIT_FAILURE = 'TENANT_SUBMIT_FAILURE';
interface TenantSubmitFailureAction {
type: typeof TENANT_SUBMIT_FAILURE;
error: string;
}
export type TenantSubmitActionTypes =
TenantSubmitStartAction |
TenantSubmitSuccessAction |
TenantSubmitFailureAction;
const tenantSubmitStart = (): TenantSubmitStartAction => ({
type: TENANT_SUBMIT_START,
});
const tenantSubmitSuccess = (
tenantJSON: ITenantJSON,
): TenantSubmitSuccessAction => ({
type: TENANT_SUBMIT_SUCCESS,
tenant: tenantJSON,
});
const tenantSubmitFailure = (error: string): TenantSubmitFailureAction => ({
type: TENANT_SUBMIT_FAILURE,
error,
});
export const submitTenant = (
userFullName: string,
userEmail: string,
userPassword: string,
siteName: string,
subdomain: string,
authenticityToken: string,
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
dispatch(tenantSubmitStart());
try {
const res = await fetch(`/tenants`, {
method: 'POST',
headers: buildRequestHeaders(authenticityToken),
body: JSON.stringify({
user: {
full_name: userFullName,
email: userEmail,
password: userPassword,
},
tenant: {
site_name: siteName,
subdomain: subdomain,
},
}),
});
const json = await res.json();
if (res.status === HttpStatus.Created) {
dispatch(tenantSubmitSuccess(json));
} else {
dispatch(tenantSubmitFailure(json.error));
}
} catch (e) {
dispatch(tenantSubmitFailure(e));
}
};

View File

@@ -0,0 +1,124 @@
export const TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH = 'TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH';
interface TenantSignUpToggleEmailAuth {
type: typeof TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH,
}
export const toggleEmailAuthTenantSignUp = (
): TenantSignUpToggleEmailAuth => ({
type: TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH,
});
// User full name
export const TENANT_SIGN_UP_CHANGE_USER_FULL_NAME = 'TENANT_SIGN_UP_CHANGE_USER_FULL_NAME';
interface TenantSignUpChangeUserFullName {
type: typeof TENANT_SIGN_UP_CHANGE_USER_FULL_NAME,
fullName: string,
}
export const changeUserFullNameTenantSignUp = (
fullName: string
): TenantSignUpChangeUserFullName => ({
type: TENANT_SIGN_UP_CHANGE_USER_FULL_NAME,
fullName,
});
// User email
export const TENANT_SIGN_UP_CHANGE_USER_EMAIL = 'TENANT_SIGN_UP_CHANGE_USER_EMAIL';
interface TenantSignUpChangeUserEmail {
type: typeof TENANT_SIGN_UP_CHANGE_USER_EMAIL,
email: string,
}
export const changeUserEmailTenantSignUp = (
email: string
): TenantSignUpChangeUserEmail => ({
type: TENANT_SIGN_UP_CHANGE_USER_EMAIL,
email,
});
// User password
export const TENANT_SIGN_UP_CHANGE_USER_PASSWORD = 'TENANT_SIGN_UP_CHANGE_USER_PASSWORD';
interface TenantSignUpChangeUserPassword {
type: typeof TENANT_SIGN_UP_CHANGE_USER_PASSWORD,
password: string,
}
export const changeUserPasswordTenantSignUp = (
password: string
): TenantSignUpChangeUserPassword => ({
type: TENANT_SIGN_UP_CHANGE_USER_PASSWORD,
password,
});
// User password confirmation
export const TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION = 'TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION';
interface TenantSignUpChangeUserPasswordConfirmation {
type: typeof TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION,
passwordConfirmation: string,
}
export const changeUserPasswordConfirmationTenantSignUp = (
passwordConfirmation: string
): TenantSignUpChangeUserPasswordConfirmation => ({
type: TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION,
passwordConfirmation,
});
// Confirm user data, proceed to step 2
export const TENANT_SIGN_UP_CONFIRM_USER_FORM = 'TENANT_SIGN_UP_CONFIRM_USER_FORM';
interface TenantSignUpConfirmUserForm {
type: typeof TENANT_SIGN_UP_CONFIRM_USER_FORM;
}
export const confirmUserFormTenantSignUp = (): TenantSignUpConfirmUserForm => ({
type: TENANT_SIGN_UP_CONFIRM_USER_FORM,
});
// Tenant site name
export const TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME = 'TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME';
interface TenantSignUpChangeTenantSiteName {
type: typeof TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME,
siteName: string,
}
export const changeTenantSiteNameTenantSignUp = (
siteName: string
): TenantSignUpChangeTenantSiteName => ({
type: TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME,
siteName,
});
// Tenant site name
export const TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN = 'TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN';
interface TenantSignUpChangeTenantSubdomain {
type: typeof TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN,
subdomain: string,
}
export const changeTenantSubdomainTenantSignUp = (
subdomain: string
): TenantSignUpChangeTenantSubdomain => ({
type: TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN,
subdomain,
});
export type TenantSignUpFormActions =
TenantSignUpToggleEmailAuth |
TenantSignUpChangeUserFullName |
TenantSignUpChangeUserEmail |
TenantSignUpChangeUserPassword |
TenantSignUpChangeUserPasswordConfirmation |
TenantSignUpConfirmUserForm |
TenantSignUpChangeTenantSiteName |
TenantSignUpChangeTenantSubdomain;

View File

@@ -0,0 +1,91 @@
import { Action } from "redux";
import { ThunkAction } from "redux-thunk";
import HttpStatus from "../../constants/http_status";
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
import ITenantJSON from "../../interfaces/json/ITenant";
import { State } from "../../reducers/rootReducer";
export const TENANT_UPDATE_START = 'TENANT_UPDATE_START';
interface TenantUpdateStartAction {
type: typeof TENANT_UPDATE_START;
}
export const TENANT_UPDATE_SUCCESS = 'TENANT_UPDATE_SUCCESS';
interface TenantUpdateSuccessAction {
type: typeof TENANT_UPDATE_SUCCESS;
tenant: ITenantJSON;
}
export const TENANT_UPDATE_FAILURE = 'TENANT_UPDATE_FAILURE';
interface TenantUpdateFailureAction {
type: typeof TENANT_UPDATE_FAILURE;
error: string;
}
export type TenantUpdateActionTypes =
TenantUpdateStartAction |
TenantUpdateSuccessAction |
TenantUpdateFailureAction;
const tenantUpdateStart = (): TenantUpdateStartAction => ({
type: TENANT_UPDATE_START,
});
const tenantUpdateSuccess = (
tenantJSON: ITenantJSON,
): TenantUpdateSuccessAction => ({
type: TENANT_UPDATE_SUCCESS,
tenant: tenantJSON,
});
const tenantUpdateFailure = (error: string): TenantUpdateFailureAction => ({
type: TENANT_UPDATE_FAILURE,
error,
});
interface UpdateTenantParams {
siteName?: string;
siteLogo?: string;
brandDisplaySetting?: string;
locale?: string;
authenticityToken: string;
}
export const updateTenant = ({
siteName = null,
siteLogo = null,
brandDisplaySetting = null,
locale = null,
authenticityToken,
}: UpdateTenantParams): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
dispatch(tenantUpdateStart());
const tenant = Object.assign({},
siteName !== null ? { site_name: siteName } : null,
siteLogo !== null ? { site_logo: siteLogo } : null,
brandDisplaySetting !== null ? { brand_display_setting: brandDisplaySetting } : null,
locale !== null ? { locale } : null
);
try {
const res = await fetch(`/tenants/0`, {
method: 'PATCH',
headers: buildRequestHeaders(authenticityToken),
body: JSON.stringify({ tenant }),
});
const json = await res.json();
if (res.status === HttpStatus.OK) {
dispatch(tenantUpdateSuccess(json));
} else {
dispatch(tenantUpdateFailure(json.error));
}
return Promise.resolve(res);
} catch (e) {
dispatch(tenantUpdateFailure(e));
return Promise.resolve(null);
}
};

View File

@@ -0,0 +1,65 @@
// siteName
export const SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME = 'SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME';
interface SiteSettingsChangeGeneralFormSiteName {
type: typeof SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME,
siteName: string,
}
export const changeSiteSettingsGeneralFormSiteName = (
siteName: string
): SiteSettingsChangeGeneralFormSiteName => ({
type: SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME,
siteName,
});
// siteLogo
export const SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO = 'SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO';
interface SiteSettingsChangeGeneralFormSiteLogo {
type: typeof SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO,
siteLogo: string,
}
export const changeSiteSettingsGeneralFormSiteLogo = (
siteLogo: string
): SiteSettingsChangeGeneralFormSiteLogo => ({
type: SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO,
siteLogo,
});
// brandDisplaySetting
export const SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING = 'SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING';
interface SiteSettingsChangeGeneralFormBrandSetting {
type: typeof SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING,
brandDisplaySetting: string,
}
export const changeSiteSettingsGeneralFormBrandSetting = (
brandDisplaySetting: string
): SiteSettingsChangeGeneralFormBrandSetting => ({
type: SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING,
brandDisplaySetting,
});
// locale
export const SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE = 'SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE';
interface SiteSettingsChangeGeneralFormLocale {
type: typeof SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE,
locale: string,
}
export const changeSiteSettingsGeneralFormLocale = (
locale: string
): SiteSettingsChangeGeneralFormLocale => ({
type: SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE,
locale,
});
export type ChangeSiteSettingsGeneralFormActionTypes =
SiteSettingsChangeGeneralFormSiteName |
SiteSettingsChangeGeneralFormSiteLogo |
SiteSettingsChangeGeneralFormBrandSetting |
SiteSettingsChangeGeneralFormLocale;

View File

@@ -80,18 +80,22 @@ class BoardForm extends React.Component<Props, State> {
const {name, description} = this.state;
return (
<div className="boardForm">
<form className="boardForm">
<div className="boardMandatoryForm">
<input
type="text"
placeholder={I18n.t('site_settings.boards.form.name')}
value={name}
onChange={e => this.onNameChange(e.target.value)}
autoFocus
className="form-control"
/>
<Button
onClick={this.onSubmit}
onClick={e => {
e.preventDefault();
this.onSubmit();
}}
className="newBoardButton"
disabled={!this.isFormValid()}
>
@@ -110,7 +114,7 @@ class BoardForm extends React.Component<Props, State> {
onChange={e => this.onDescriptionChange(e.target.value)}
className="form-control"
/>
</div>
</form>
);
}
}

View File

@@ -0,0 +1,166 @@
import * as React from 'react';
import I18n from 'i18n-js';
import Box from '../../common/Box';
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
import { ISiteSettingsGeneralForm } from '../../../reducers/SiteSettings/generalReducer';
import Button from '../../common/Button';
import HttpStatus from '../../../constants/http_status';
import {
TENANT_BRAND_NAME_AND_LOGO,
TENANT_BRAND_NAME_ONLY,
TENANT_BRAND_LOGO_ONLY,
TENANT_BRAND_NONE,
} from '../../../interfaces/ITenant';
interface Props {
originForm: ISiteSettingsGeneralForm;
authenticityToken: string;
form: ISiteSettingsGeneralForm;
areDirty: boolean;
areLoading: boolean;
areUpdating: boolean;
error: string;
requestTenant(): void;
updateTenant(
siteName: string,
siteLogo: string,
brandDisplaySetting: string,
locale: string,
authenticityToken: string
): Promise<any>;
handleChangeSiteName(siteName: string): void;
handleChangeSiteLogo(siteLogo: string): void;
handleChangeBrandDisplaySetting(brandDisplaySetting: string)
handleChangeLocale(locale: string): void;
}
class GeneralSiteSettingsP extends React.Component<Props> {
constructor(props: Props) {
super(props);
this._handleUpdateTenant = this._handleUpdateTenant.bind(this);
}
componentDidMount() {
this.props.requestTenant();
}
_handleUpdateTenant() {
const { siteName, siteLogo, brandDisplaySetting, locale } = this.props.form;
this.props.updateTenant(
siteName,
siteLogo,
brandDisplaySetting,
locale,
this.props.authenticityToken,
).then(res => {
if (res?.status !== HttpStatus.OK) return;
window.location.reload();
});
}
render() {
const {
originForm,
form,
areDirty,
areLoading,
areUpdating,
error,
handleChangeSiteName,
handleChangeSiteLogo,
handleChangeBrandDisplaySetting,
handleChangeLocale,
} = this.props;
return (
<>
<Box>
<h2>{ I18n.t('site_settings.general.title') }</h2>
<form>
<div className="formRow">
<div className="formGroup col-4">
<label htmlFor="siteName">{ I18n.t('site_settings.general.site_name') }</label>
<input
type="text"
value={areLoading ? originForm.siteName : form.siteName}
onChange={e => handleChangeSiteName(e.target.value)}
id="siteName"
className="formControl"
/>
</div>
<div className="formGroup col-4">
<label htmlFor="siteLogo">{ I18n.t('site_settings.general.site_logo') }</label>
<input
type="text"
value={areLoading ? originForm.siteLogo : form.siteLogo}
onChange={e => handleChangeSiteLogo(e.target.value)}
id="siteLogo"
className="formControl"
/>
</div>
<div className="formGroup col-4">
<label htmlFor="brandSetting">{ I18n.t('site_settings.general.brand_setting') }</label>
<select
value={form.brandDisplaySetting || originForm.brandDisplaySetting}
onChange={e => handleChangeBrandDisplaySetting(e.target.value)}
id="brandSetting"
className="selectPicker"
>
<option value={TENANT_BRAND_NAME_AND_LOGO}>
{ I18n.t('site_settings.general.brand_setting_both') }
</option>
<option value={TENANT_BRAND_NAME_ONLY}>
{ I18n.t('site_settings.general.brand_setting_name') }
</option>
<option value={TENANT_BRAND_LOGO_ONLY}>
{ I18n.t('site_settings.general.brand_setting_logo') }
</option>
<option value={TENANT_BRAND_NONE}>
{ I18n.t('site_settings.general.brand_setting_none') }
</option>
</select>
</div>
</div>
<div className="formGroup">
<label htmlFor="locale">{ I18n.t('site_settings.general.locale') }</label>
<select
value={form.locale || originForm.locale}
onChange={e => handleChangeLocale(e.target.value)}
id="locale"
className="selectPicker"
>
<option value="en">🇬🇧 English</option>
<option value="it">🇮🇹 Italiano</option>
</select>
</div>
</form>
<br />
<Button
onClick={this._handleUpdateTenant}
disabled={!areDirty}
>
{ I18n.t('common.buttons.update') }
</Button>
</Box>
<SiteSettingsInfoBox areUpdating={areLoading || areUpdating} error={error} areDirty={areDirty} />
</>
);
}
}
export default GeneralSiteSettingsP;

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import GeneralSiteSettings from '../../../containers/GeneralSiteSettings';
import createStoreHelper from '../../../helpers/createStore';
import { State } from '../../../reducers/rootReducer';
import { ISiteSettingsGeneralForm } from '../../../reducers/SiteSettings/generalReducer';
interface Props {
originForm: ISiteSettingsGeneralForm;
authenticityToken: string;
}
class GeneralSiteSettingsRoot extends React.Component<Props> {
store: Store<State, any>;
constructor(props: Props) {
super(props);
this.store = createStoreHelper();
}
render() {
return (
<Provider store={this.store}>
<GeneralSiteSettings
originForm={this.props.originForm}
authenticityToken={this.props.authenticityToken}
/>
</Provider>
);
}
}
export default GeneralSiteSettingsRoot;

View File

@@ -87,12 +87,13 @@ class PostStatusForm extends React.Component<Props, State> {
const {name, color} = this.state;
return (
<div className="postStatusForm">
<form className="postStatusForm">
<input
type="text"
placeholder={I18n.t('site_settings.post_statuses.form.name')}
value={name}
onChange={e => this.onNameChange(e.target.value)}
autoFocus
className="form-control"
/>
@@ -104,7 +105,10 @@ class PostStatusForm extends React.Component<Props, State> {
/>
<Button
onClick={this.onSubmit}
onClick={e => {
e.preventDefault();
this.onSubmit();
}}
className="newPostStatusButton"
disabled={!this.isFormValid()}
>
@@ -115,7 +119,7 @@ class PostStatusForm extends React.Component<Props, State> {
I18n.t('common.buttons.update')
}
</Button>
</div>
</form>
);
}
}

View File

@@ -0,0 +1,21 @@
import * as React from 'react';
import I18n from 'i18n-js';
import Box from '../common/Box';
interface Props {
subdomain: string;
userEmail: string;
}
const ConfirmSignUpPage = ({
subdomain,
userEmail,
}: Props) => (
<Box>
<h3>{ I18n.t('signup.step3.title') }</h3>
<p>{ I18n.t('signup.step3.message', { email: userEmail, subdomain: `${subdomain}.astuto.io` }) }</p>
</Box>
);
export default ConfirmSignUpPage;

View File

@@ -0,0 +1,92 @@
import * as React from 'react';
import I18n from 'i18n-js';
import Box from '../common/Box';
import { TenantSignUpTenantFormState } from '../../reducers/tenantSignUpReducer';
import Button from '../common/Button';
import Spinner from '../common/Spinner';
import { DangerText } from '../common/CustomTexts';
interface Props {
tenantForm: TenantSignUpTenantFormState;
handleChangeTenantSiteName(siteName: string): void;
handleChangeTenantSubdomain(subdomain: string): void;
isSubmitting: boolean;
error: string;
handleSubmit(): void;
}
class TenantSignUpForm extends React.Component<Props> {
form: any;
constructor(props: Props) {
super(props);
this.form = React.createRef();
}
render() {
const {
tenantForm,
handleChangeTenantSiteName,
handleChangeTenantSubdomain,
isSubmitting,
error,
handleSubmit,
} = this.props;
return (
<Box customClass="tenantSignUpStep2">
<h3>{ I18n.t('signup.step2.title') }</h3>
<form ref={this.form}>
<div className="formRow">
<input
type="text"
autoFocus
value={tenantForm.siteName}
onChange={e => handleChangeTenantSiteName(e.target.value)}
placeholder={I18n.t('signup.step2.site_name')}
required
id="tenantSiteName"
className="formControl"
/>
</div>
<div className="formRow">
<div className="input-group">
<input
type="text"
value={tenantForm.subdomain}
onChange={e => handleChangeTenantSubdomain(e.target.value)}
placeholder={I18n.t('signup.step2.subdomain')}
required
id="tenantSubdomain"
className="formControl"
/>
<div className="input-group-append">
<div className="input-group-text">.astuto.io</div>
</div>
</div>
</div>
<Button
onClick={e => {
e.preventDefault();
handleSubmit();
}}
className="tenantConfirm"
>
{ isSubmitting ? <Spinner /> : I18n.t('signup.step2.create_button') }
</Button>
{ error !== '' && <DangerText>{ error }</DangerText> }
</form>
</Box>
);
}
}
export default TenantSignUpForm;

View File

@@ -0,0 +1,124 @@
import * as React from 'react';
import { TenantSignUpTenantFormState, TenantSignUpUserFormState } from '../../reducers/tenantSignUpReducer';
import ConfirmSignUpPage from './ConfirmSignUpPage';
import TenantSignUpForm from './TenantSignUpForm';
import UserSignUpForm from './UserSignUpForm';
interface Props {
authenticityToken: string;
currentStep: number;
emailAuth: boolean;
isSubmitting: boolean;
error: string;
toggleEmailAuth(): void;
userForm: TenantSignUpUserFormState;
handleChangeUserFullName(fullName: string): void;
handleChangeUserEmail(email: string): void;
handleChangeUserPassword(password: string): void;
handleChangeUserPasswordConfirmation(passwordConfirmation: string): void;
handleUserFormConfirm(): void;
tenantForm: TenantSignUpTenantFormState;
handleChangeTenantSiteName(siteName: string): void;
handleChangeTenantSubdomain(subdomain: string): void;
handleSubmit(
userFullName: string,
userEmail: string,
userPassword: string,
siteName: string,
subdomain: string,
authenticityToken: string,
): void;
}
class TenantSignUpP extends React.Component<Props> {
constructor(props: Props) {
super(props);
this._handleSubmit = this._handleSubmit.bind(this);
}
_handleSubmit() {
const { userForm, tenantForm, handleSubmit } = this.props;
handleSubmit(
userForm.fullName,
userForm.email,
userForm.password,
tenantForm.siteName,
tenantForm.subdomain,
this.props.authenticityToken,
);
}
render() {
const {
currentStep,
emailAuth,
toggleEmailAuth,
userForm,
handleChangeUserFullName,
handleChangeUserEmail,
handleChangeUserPassword,
handleChangeUserPasswordConfirmation,
handleUserFormConfirm,
tenantForm,
handleChangeTenantSiteName,
handleChangeTenantSubdomain,
isSubmitting,
error,
} = this.props;
return (
<div className="tenantSignUpContainer">
{
(currentStep === 1 || currentStep === 2) &&
<UserSignUpForm
currentStep={currentStep}
emailAuth={emailAuth}
toggleEmailAuth={toggleEmailAuth}
userForm={userForm}
handleChangeUserFullName={handleChangeUserFullName}
handleChangeUserEmail={handleChangeUserEmail}
handleChangeUserPassword={handleChangeUserPassword}
handleChangeUserPasswordConfirmation={handleChangeUserPasswordConfirmation}
handleUserFormConfirm={handleUserFormConfirm}
/>
}
{
currentStep === 2 &&
<TenantSignUpForm
tenantForm={tenantForm}
handleChangeTenantSiteName={handleChangeTenantSiteName}
handleChangeTenantSubdomain={handleChangeTenantSubdomain}
isSubmitting={isSubmitting}
error={error}
handleSubmit={this._handleSubmit}
/>
}
{
currentStep === 3 &&
<ConfirmSignUpPage
subdomain={tenantForm.subdomain}
userEmail={userForm.email}
/>
}
</div>
);
}
}
export default TenantSignUpP;

View File

@@ -0,0 +1,147 @@
import * as React from 'react';
import I18n from 'i18n-js';
import Box from '../common/Box';
import Button from '../common/Button';
import { TenantSignUpUserFormState } from '../../reducers/tenantSignUpReducer';
interface Props {
currentStep: number;
emailAuth: boolean;
toggleEmailAuth(): void;
userForm: TenantSignUpUserFormState;
handleChangeUserFullName(fullName: string): void;
handleChangeUserEmail(email: string): void;
handleChangeUserPassword(password: string): void;
handleChangeUserPasswordConfirmation(passwordConfirmation: string): void;
handleUserFormConfirm(): void;
}
class UserSignUpForm extends React.Component<Props> {
form: any;
constructor(props: Props) {
super(props);
this.form = React.createRef();
}
validateUserForm(): boolean {
let isValid: boolean = this.form.current.reportValidity();
if (this.validateUserPasswordConfirmation() === false)
isValid = false;
return isValid;
}
validateUserPasswordConfirmation(): boolean {
const isValid = this.props.userForm.password === this.props.userForm.passwordConfirmation;
return isValid;
}
render() {
const {
currentStep,
emailAuth,
toggleEmailAuth,
userForm,
handleChangeUserFullName,
handleChangeUserEmail,
handleChangeUserPassword,
handleChangeUserPasswordConfirmation,
handleUserFormConfirm,
} = this.props;
return (
<Box customClass="tenantSignUpStep1">
<h3>{ I18n.t('signup.step1.title') }</h3>
{
currentStep === 1 && !emailAuth &&
<Button className="emailAuth" onClick={toggleEmailAuth}>
{ I18n.t('signup.step1.email_auth') }
</Button>
}
{
currentStep === 1 && emailAuth &&
<form ref={this.form}>
<div className="formRow">
<input
type="text"
autoFocus
value={userForm.fullName}
onChange={e => handleChangeUserFullName(e.target.value)}
placeholder={I18n.t('common.forms.auth.full_name')}
required
id="userFullName"
className="formControl"
/>
</div>
<div className="formRow">
<input
type="email"
value={userForm.email}
onChange={e => handleChangeUserEmail(e.target.value)}
placeholder={I18n.t('common.forms.auth.email')}
required
id="userEmail"
className="formControl"
/>
</div>
<div className="formRow">
<div className="formGroup col-6">
<input
type="password"
value={userForm.password}
onChange={e => handleChangeUserPassword(e.target.value)}
placeholder={I18n.t('common.forms.auth.password')}
required
minLength={6}
maxLength={128}
id="userPassword"
className="formControl"
/>
</div>
<div className="formGroup col-6">
<input
type="password"
value={userForm.passwordConfirmation}
onChange={e => handleChangeUserPasswordConfirmation(e.target.value)}
placeholder={I18n.t('common.forms.auth.password_confirmation')}
required
minLength={6}
maxLength={128}
id="userPasswordConfirmation"
className={`formControl${userForm.passwordConfirmationError ? ' invalid' : ''}`}
/>
</div>
</div>
<Button
onClick={e => {
e.preventDefault();
this.validateUserForm() && handleUserFormConfirm();
}}
className="userConfirm"
>
{ I18n.t('common.buttons.confirm') }
</Button>
</form>
}
{
currentStep === 2 &&
<p><b>{userForm.fullName}</b> ({userForm.email})</p>
}
</Box>
);
}
}
export default UserSignUpForm;

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import createStoreHelper from '../../helpers/createStore';
import TenantSignUp from '../../containers/TenantSignUp';
import { Store } from 'redux';
import { State } from '../../reducers/rootReducer';
interface Props {
authenticityToken: string;
}
class TenantSignUpRoot extends React.Component<Props> {
store: Store<State, any>;
constructor(props: Props) {
super(props);
this.store = createStoreHelper();
}
render() {
const { authenticityToken } = this.props;
return (
<Provider store={this.store}>
<TenantSignUp
authenticityToken={authenticityToken}
/>
</Provider>
);
}
}
export default TenantSignUpRoot;

View File

@@ -7,9 +7,10 @@ import Box from './Box';
interface Props {
areUpdating: boolean;
error: string;
areDirty?: boolean;
}
const SiteSettingsInfoBox = ({ areUpdating, error }: Props) => (
const SiteSettingsInfoBox = ({ areUpdating, error, areDirty = false }: Props) => (
<Box customClass="siteSettingsInfo">
{
areUpdating ?
@@ -17,10 +18,13 @@ const SiteSettingsInfoBox = ({ areUpdating, error }: Props) => (
:
error ?
<span className="error">
{I18n.t('site_settings.info_box.error', { message: JSON.stringify(error) })}
{ I18n.t('site_settings.info_box.error', { message: JSON.stringify(error) }) }
</span>
:
<span>{I18n.t('site_settings.info_box.up_to_date')}</span>
areDirty ?
<span className="warning">{ I18n.t('site_settings.info_box.dirty') }</span>
:
<span>{ I18n.t('site_settings.info_box.up_to_date') }</span>
}
</Box>
);

View File

@@ -1 +1 @@
export const POSTS_PER_PAGE = parseInt(process.env.POSTS_PER_PAGE);
export const POSTS_PER_PAGE = 15;

View File

@@ -0,0 +1,63 @@
import { connect } from "react-redux";
import { requestTenant } from "../actions/Tenant/requestTenant";
import {
changeSiteSettingsGeneralFormBrandSetting,
changeSiteSettingsGeneralFormLocale,
changeSiteSettingsGeneralFormSiteLogo,
changeSiteSettingsGeneralFormSiteName
} from "../actions/changeSiteSettingsGeneralForm";
import GeneralSiteSettingsP from "../components/SiteSettings/General/GeneralSiteSettingsP";
import { State } from "../reducers/rootReducer";
import { updateTenant } from "../actions/Tenant/updateTenant";
const mapStateToProps = (state: State) => ({
form: state.siteSettings.general.form,
areDirty: state.siteSettings.general.areDirty,
areLoading: state.siteSettings.general.areLoading,
areUpdating: state.siteSettings.general.areUpdating,
error: state.siteSettings.general.error,
});
const mapDispatchToProps = (dispatch: any) => ({
requestTenant() {
dispatch(requestTenant());
},
updateTenant(
siteName: string,
siteLogo: string,
brandDisplaySetting: string,
locale: string,
authenticityToken: string
): Promise<any> {
return dispatch(updateTenant({
siteName,
siteLogo,
brandDisplaySetting,
locale,
authenticityToken,
}));
},
handleChangeSiteName(siteName: string) {
dispatch(changeSiteSettingsGeneralFormSiteName(siteName));
},
handleChangeSiteLogo(siteLogo: string) {
dispatch(changeSiteSettingsGeneralFormSiteLogo(siteLogo));
},
handleChangeBrandDisplaySetting(brandDisplaySetting: string) {
dispatch(changeSiteSettingsGeneralFormBrandSetting(brandDisplaySetting));
},
handleChangeLocale(locale: string) {
dispatch(changeSiteSettingsGeneralFormLocale(locale));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(GeneralSiteSettingsP);

View File

@@ -0,0 +1,82 @@
import { connect } from "react-redux";
import TenantSignUpP from "../components/TenantSignUp/TenantSignUpP";
import { State } from "../reducers/rootReducer";
import {
changeTenantSiteNameTenantSignUp,
changeTenantSubdomainTenantSignUp,
changeUserEmailTenantSignUp,
changeUserFullNameTenantSignUp,
changeUserPasswordConfirmationTenantSignUp,
changeUserPasswordTenantSignUp,
confirmUserFormTenantSignUp,
toggleEmailAuthTenantSignUp
} from "../actions/Tenant/tenantSignUpFormActions";
import { submitTenant } from "../actions/Tenant/submitTenant";
const mapStateToProps = (state: State) => ({
currentStep: state.tenantSignUp.currentStep,
emailAuth: state.tenantSignUp.emailAuth,
isSubmitting: state.tenantSignUp.isSubmitting,
error: state.tenantSignUp.error,
userForm: state.tenantSignUp.userForm,
tenantForm: state.tenantSignUp.tenantForm,
});
const mapDispatchToProps = (dispatch: any) => ({
toggleEmailAuth() {
dispatch(toggleEmailAuthTenantSignUp());
},
handleChangeUserFullName(fullName: string) {
dispatch(changeUserFullNameTenantSignUp(fullName));
},
handleChangeUserEmail(email: string) {
dispatch(changeUserEmailTenantSignUp(email));
},
handleChangeUserPassword(password: string) {
dispatch(changeUserPasswordTenantSignUp(password));
},
handleChangeUserPasswordConfirmation(passwordConfirmation: string) {
dispatch(changeUserPasswordConfirmationTenantSignUp(passwordConfirmation));
},
handleUserFormConfirm() {
dispatch(confirmUserFormTenantSignUp());
},
handleChangeTenantSiteName(siteName: string) {
dispatch(changeTenantSiteNameTenantSignUp(siteName));
},
handleChangeTenantSubdomain(subdomain: string) {
dispatch(changeTenantSubdomainTenantSignUp(subdomain));
},
handleSubmit(
userFullName: string,
userEmail: string,
userPassword: string,
siteName: string,
subdomain: string,
authenticityToken: string,
) {
dispatch(submitTenant(
userFullName,
userEmail,
userPassword,
siteName,
subdomain,
authenticityToken,
));
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TenantSignUpP);

View File

@@ -0,0 +1,21 @@
// Brand display setting
export const TENANT_BRAND_NAME_AND_LOGO = 'name_and_logo';
export const TENANT_BRAND_NAME_ONLY = 'name_only';
export const TENANT_BRAND_LOGO_ONLY = 'logo_only';
export const TENANT_BRAND_NONE = 'no_name_no_logo';
export type TenantBrandDisplaySetting =
typeof TENANT_BRAND_NAME_AND_LOGO |
typeof TENANT_BRAND_NAME_ONLY |
typeof TENANT_BRAND_LOGO_ONLY |
typeof TENANT_BRAND_NONE;
interface ITenant {
id: number;
siteName: string;
siteLogo: string;
brandDisplaySetting: TenantBrandDisplaySetting;
locale: string;
}
export default ITenant;

View File

@@ -0,0 +1,9 @@
interface ITenantJSON {
id: number;
site_name: string;
site_logo: string;
brand_display_setting: string;
locale: string;
}
export default ITenantJSON;

View File

@@ -0,0 +1,140 @@
import {
TenantRequestActionTypes,
TENANT_REQUEST_START,
TENANT_REQUEST_SUCCESS,
TENANT_REQUEST_FAILURE,
} from "../../actions/Tenant/requestTenant";
import {
TenantUpdateActionTypes,
TENANT_UPDATE_START,
TENANT_UPDATE_SUCCESS,
TENANT_UPDATE_FAILURE,
} from '../../actions/Tenant/updateTenant';
import {
ChangeSiteSettingsGeneralFormActionTypes,
SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME,
SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO,
SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING,
SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE,
} from '../../actions/changeSiteSettingsGeneralForm';
export interface ISiteSettingsGeneralForm {
siteName: string;
siteLogo: string;
brandDisplaySetting: string;
locale: string;
}
export interface SiteSettingsGeneralState {
form: ISiteSettingsGeneralForm,
areDirty: boolean;
areLoading: boolean;
areUpdating: boolean;
error: string;
}
const initialState: SiteSettingsGeneralState = {
form: {
siteName: '',
siteLogo: '',
brandDisplaySetting: '',
locale: '',
},
areDirty: false,
areLoading: false,
areUpdating: false,
error: '',
};
const siteSettingsGeneralReducer = (
state = initialState,
action:
TenantRequestActionTypes |
TenantUpdateActionTypes |
ChangeSiteSettingsGeneralFormActionTypes
) => {
switch (action.type) {
case TENANT_REQUEST_START:
return {
...state,
areLoading: true,
};
case TENANT_UPDATE_START:
return {
...state,
areUpdating: true,
};
case TENANT_REQUEST_SUCCESS:
return {
...state,
form: {
siteName: action.tenant.site_name,
siteLogo: action.tenant.site_logo,
brandDisplaySetting: action.tenant.brand_display_setting,
locale: action.tenant.locale,
},
areDirty: false,
areLoading: false,
error: '',
};
case TENANT_UPDATE_SUCCESS:
return {
...state,
areDirty: false,
areUpdating: false,
error: '',
};
case TENANT_REQUEST_FAILURE:
return {
...state,
areLoading: false,
error: action.error,
};
case TENANT_UPDATE_FAILURE:
return {
...state,
areUpdating: false,
error: action.error,
};
case SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME:
return {
...state,
form: { ...state.form, siteName: action.siteName },
areDirty: true,
};
case SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO:
return {
...state,
form: { ...state.form, siteLogo: action.siteLogo },
areDirty: true,
};
case SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING:
return {
...state,
form: { ...state.form, brandDisplaySetting: action.brandDisplaySetting },
areDirty: true,
};
case SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE:
return {
...state,
form: { ...state.form, locale: action.locale },
areDirty: true,
};
default:
return state;
}
}
export default siteSettingsGeneralReducer;

View File

@@ -1,3 +1,10 @@
import {
PostStatusesRequestActionTypes,
POST_STATUSES_REQUEST_START,
POST_STATUSES_REQUEST_SUCCESS,
POST_STATUSES_REQUEST_FAILURE,
} from '../../actions/PostStatus/requestPostStatuses';
import {
PostStatusOrderUpdateActionTypes,
POSTSTATUS_ORDER_UPDATE_START,
@@ -38,12 +45,15 @@ const initialState: SiteSettingsPostStatusesState = {
const siteSettingsPostStatusesReducer = (
state = initialState,
action: PostStatusOrderUpdateActionTypes |
action:
PostStatusesRequestActionTypes |
PostStatusOrderUpdateActionTypes |
PostStatusDeleteActionTypes |
PostStatusSubmitActionTypes |
PostStatusUpdateActionTypes
): SiteSettingsPostStatusesState => {
switch (action.type) {
case POST_STATUSES_REQUEST_START:
case POSTSTATUS_SUBMIT_START:
case POSTSTATUS_UPDATE_START:
case POSTSTATUS_ORDER_UPDATE_START:
@@ -53,6 +63,7 @@ const siteSettingsPostStatusesReducer = (
areUpdating: true,
};
case POST_STATUSES_REQUEST_SUCCESS:
case POSTSTATUS_SUBMIT_SUCCESS:
case POSTSTATUS_UPDATE_SUCCESS:
case POSTSTATUS_ORDER_UPDATE_SUCCESS:
@@ -63,6 +74,7 @@ const siteSettingsPostStatusesReducer = (
error: '',
};
case POST_STATUSES_REQUEST_FAILURE:
case POSTSTATUS_SUBMIT_FAILURE:
case POSTSTATUS_UPDATE_FAILURE:
case POSTSTATUS_ORDER_UPDATE_FAILURE:

View File

@@ -1,5 +1,7 @@
import { combineReducers } from 'redux';
import tenantSignUpReducer from './tenantSignUpReducer';
import postsReducer from './postsReducer';
import boardsReducer from './boardsReducer';
import postStatusesReducer from './postStatusesReducer';
@@ -8,6 +10,8 @@ import currentPostReducer from './currentPostReducer';
import siteSettingsReducer from './siteSettingsReducer';
const rootReducer = combineReducers({
tenantSignUp: tenantSignUpReducer,
posts: postsReducer,
boards: boardsReducer,
postStatuses: postStatusesReducer,

View File

@@ -1,3 +1,25 @@
import {
TenantRequestActionTypes,
TENANT_REQUEST_START,
TENANT_REQUEST_SUCCESS,
TENANT_REQUEST_FAILURE,
} from '../actions/Tenant/requestTenant';
import {
TenantUpdateActionTypes,
TENANT_UPDATE_START,
TENANT_UPDATE_SUCCESS,
TENANT_UPDATE_FAILURE,
} from '../actions/Tenant/updateTenant';
import {
ChangeSiteSettingsGeneralFormActionTypes,
SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME,
SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO,
SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING,
SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE,
} from '../actions/changeSiteSettingsGeneralForm';
import {
BoardsRequestActionTypes,
BOARDS_REQUEST_START,
@@ -33,6 +55,13 @@ import {
BOARD_DELETE_FAILURE,
} from '../actions/Board/deleteBoard';
import {
PostStatusesRequestActionTypes,
POST_STATUSES_REQUEST_START,
POST_STATUSES_REQUEST_SUCCESS,
POST_STATUSES_REQUEST_FAILURE,
} from '../actions/PostStatus/requestPostStatuses';
import {
PostStatusOrderUpdateActionTypes,
POSTSTATUS_ORDER_UPDATE_START,
@@ -75,12 +104,14 @@ import {
USER_UPDATE_FAILURE,
} from '../actions/User/updateUser';
import siteSettingsGeneralReducer, { SiteSettingsGeneralState } from './SiteSettings/generalReducer';
import siteSettingsBoardsReducer, { SiteSettingsBoardsState } from './SiteSettings/boardsReducer';
import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from './SiteSettings/postStatusesReducer';
import siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
interface SiteSettingsState {
general: SiteSettingsGeneralState;
boards: SiteSettingsBoardsState;
postStatuses: SiteSettingsPostStatusesState;
roadmap: SiteSettingsRoadmapState;
@@ -88,6 +119,7 @@ interface SiteSettingsState {
}
const initialState: SiteSettingsState = {
general: siteSettingsGeneralReducer(undefined, {} as any),
boards: siteSettingsBoardsReducer(undefined, {} as any),
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
@@ -97,11 +129,15 @@ const initialState: SiteSettingsState = {
const siteSettingsReducer = (
state = initialState,
action:
TenantRequestActionTypes |
TenantUpdateActionTypes |
ChangeSiteSettingsGeneralFormActionTypes |
BoardsRequestActionTypes |
BoardSubmitActionTypes |
BoardUpdateActionTypes |
BoardOrderUpdateActionTypes |
BoardDeleteActionTypes |
PostStatusesRequestActionTypes |
PostStatusOrderUpdateActionTypes |
PostStatusDeleteActionTypes |
PostStatusSubmitActionTypes |
@@ -110,6 +146,21 @@ const siteSettingsReducer = (
UserUpdateActionTypes
): SiteSettingsState => {
switch (action.type) {
case TENANT_REQUEST_START:
case TENANT_REQUEST_SUCCESS:
case TENANT_REQUEST_FAILURE:
case TENANT_UPDATE_START:
case TENANT_UPDATE_SUCCESS:
case TENANT_UPDATE_FAILURE:
case SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_NAME:
case SITE_SETTINGS_CHANGE_GENERAL_FORM_SITE_LOGO:
case SITE_SETTINGS_CHANGE_GENERAL_FORM_BRAND_SETTING:
case SITE_SETTINGS_CHANGE_GENERAL_FORM_LOCALE:
return {
...state,
general: siteSettingsGeneralReducer(state.general, action),
};
case BOARDS_REQUEST_START:
case BOARDS_REQUEST_SUCCESS:
case BOARDS_REQUEST_FAILURE:
@@ -130,6 +181,9 @@ const siteSettingsReducer = (
boards: siteSettingsBoardsReducer(state.boards, action),
};
case POST_STATUSES_REQUEST_START:
case POST_STATUSES_REQUEST_SUCCESS:
case POST_STATUSES_REQUEST_FAILURE:
case POSTSTATUS_SUBMIT_START:
case POSTSTATUS_SUBMIT_SUCCESS:
case POSTSTATUS_SUBMIT_FAILURE:

View File

@@ -0,0 +1,145 @@
import {
TenantSignUpFormActions,
TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH,
TENANT_SIGN_UP_CHANGE_USER_FULL_NAME,
TENANT_SIGN_UP_CHANGE_USER_EMAIL,
TENANT_SIGN_UP_CHANGE_USER_PASSWORD,
TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION,
TENANT_SIGN_UP_CONFIRM_USER_FORM,
TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME,
TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN,
} from '../actions/Tenant/tenantSignUpFormActions';
import {
TenantSubmitActionTypes,
TENANT_SUBMIT_START,
TENANT_SUBMIT_SUCCESS,
TENANT_SUBMIT_FAILURE,
} from '../actions/Tenant/submitTenant';
export interface TenantSignUpUserFormState {
fullName: string;
email: string;
password: string;
passwordConfirmation: string;
passwordConfirmationError: boolean;
}
export interface TenantSignUpTenantFormState {
siteName: string;
subdomain: string;
}
export interface TenantSignUpState {
currentStep: number;
emailAuth: boolean;
isSubmitting: boolean;
error: string;
userForm: TenantSignUpUserFormState;
tenantForm: TenantSignUpTenantFormState;
}
const initialState: TenantSignUpState = {
currentStep: 1,
emailAuth: false,
isSubmitting: false,
error: '',
userForm: {
fullName: '',
email: '',
password: '',
passwordConfirmation: '',
passwordConfirmationError: false,
},
tenantForm: {
siteName: '',
subdomain: '',
},
};
const tenantSignUpReducer = (
state = initialState,
action: TenantSignUpFormActions | TenantSubmitActionTypes,
) => {
switch (action.type) {
case TENANT_SIGN_UP_TOGGLE_EMAIL_AUTH:
return {
...state,
emailAuth: !state.emailAuth,
};
case TENANT_SIGN_UP_CHANGE_USER_FULL_NAME:
return {
...state,
userForm: { ...state.userForm, fullName: action.fullName },
};
case TENANT_SIGN_UP_CHANGE_USER_EMAIL:
return {
...state,
userForm: { ...state.userForm, email: action.email },
};
case TENANT_SIGN_UP_CHANGE_USER_PASSWORD:
return {
...state,
userForm: { ...state.userForm, password: action.password },
};
case TENANT_SIGN_UP_CHANGE_USER_PASSWORD_CONFIRMATION:
return {
...state,
userForm: {
...state.userForm,
passwordConfirmation: action.passwordConfirmation,
passwordConfirmationError: state.userForm.password !== action.passwordConfirmation,
},
};
case TENANT_SIGN_UP_CONFIRM_USER_FORM:
return {
...state,
currentStep: 2,
};
case TENANT_SIGN_UP_CHANGE_TENANT_SITE_NAME:
return {
...state,
tenantForm: { ...state.tenantForm, siteName: action.siteName },
};
case TENANT_SIGN_UP_CHANGE_TENANT_SUBDOMAIN:
return {
...state,
tenantForm: { ...state.tenantForm, subdomain: action.subdomain },
};
case TENANT_SUBMIT_START:
return {
...state,
isSubmitting: true,
};
case TENANT_SUBMIT_SUCCESS:
return {
...state,
currentStep: 3,
isSubmitting: false,
error: '',
};
case TENANT_SUBMIT_FAILURE:
return {
...state,
isSubmitting: false,
error: action.error,
};
default:
return state;
}
}
export default tenantSignUpReducer;

View File

@@ -36,6 +36,25 @@
a { color: $primary-color; }
}
.formRow {
@extend .form-row;
margin-top: 10px;
margin-bottom: 10px;
}
.formGroup {
@extend .form-group;
}
.formControl {
@extend .form-control;
&.invalid {
border-color: red;
}
}
.switch {
@extend
.custom-control-input;

View File

@@ -30,7 +30,7 @@
.align-top,
.mr-2;
width: 36px;
height: 36px;
}
}

View File

@@ -142,6 +142,11 @@
max-width: 960px;
}
.smallContainer {
max-width: 540px;
margin: 16px auto;
}
.turbolinks-progress-bar {
background-color: $primary-color;
height: 2px;

View File

@@ -26,6 +26,7 @@
.postStatusForm {
@extend
.d-flex,
.flex-grow-1,
.m-2;
column-gap: 8px;

View File

@@ -4,4 +4,8 @@
.error {
color: red;
}
.warning {
color: #fd7e14;
}
}

View File

@@ -0,0 +1,3 @@
.tenantSignUpContainer {
@extend .smallContainer;
}

View File

@@ -9,6 +9,7 @@
@import 'common/scroll_shadows';
/* Components */
@import 'components/TenantSignUp';
@import 'components/Board';
@import 'components/Comments';
@import 'components/LikeButton';