[NEW] Setup Wizard and Page not found, using React components (#15204)
parent
afa986bdd2
commit
2c49313a76
@ -1,18 +0,0 @@ |
|||||||
<template name="setupWizardFinal"> |
|
||||||
<div class="setup-wizard"> |
|
||||||
<div class="setup-wizard-final"> |
|
||||||
<header class="setup-wizard-info__header setup-wizard-final__header"> |
|
||||||
<img class="setup-wizard-info__header-logo" src="images/logo/logo.svg"> |
|
||||||
</header> |
|
||||||
<main class="setup-wizard-final__box"> |
|
||||||
<span class="setup-wizard-forms__header-step">{{_ "Launched_successfully"}}</span> |
|
||||||
<h1 class="setup-wizard-info__content-title setup-wizard-final__box-title">{{_ "Your_workspace_is_ready"}}</h1> |
|
||||||
<span class="setup-wizard-final__link-text">{{_ "Your_server_link"}}</span> |
|
||||||
<span class="setup-wizard-final__link">{{siteUrl}}</span> |
|
||||||
<button class="rc-button rc-button--primary js-finish"> |
|
||||||
<span>{{_ "Go_to_your_workspace"}}</span> |
|
||||||
</button> |
|
||||||
</main> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
@ -1,4 +0,0 @@ |
|||||||
import './setupWizard.html'; |
|
||||||
import './setupWizard'; |
|
||||||
import './final.html'; |
|
||||||
import './final'; |
|
||||||
@ -1,214 +0,0 @@ |
|||||||
<template name="setupWizard"> |
|
||||||
<div class="rc-old connection-status"> |
|
||||||
{{> status}} |
|
||||||
</div> |
|
||||||
<div class="setup-wizard"> |
|
||||||
{{> setupWizardInfo infoArgs}} |
|
||||||
|
|
||||||
<section class="setup-wizard-forms"> |
|
||||||
<div class="setup-wizard-forms__wrapper"> |
|
||||||
<form class="setup-wizard-forms__box {{formLoadStateClass}}" novalidate> |
|
||||||
<header class="setup-wizard-forms__header"> |
|
||||||
<span class="setup-wizard-forms__header-step">{{_ "Step"}} {{currentStep}}</span> |
|
||||||
<h1 class="setup-wizard-forms__header-title">{{_ currentStepTitle}}</h1> |
|
||||||
</header> |
|
||||||
|
|
||||||
<main class="setup-wizard-forms__content"> |
|
||||||
{{> setupWizardAdminInfo adminInfoArgs}} |
|
||||||
{{> setupWizardCustomStep (customStepArgs 2)}} |
|
||||||
{{> setupWizardCustomStep (customStepArgs 3)}} |
|
||||||
{{> setupWizardRegisterServer registerServerArgs}} |
|
||||||
</main> |
|
||||||
|
|
||||||
<footer class="setup-wizard-forms__footer"> |
|
||||||
{{#if showBackButton}} |
|
||||||
<button type="button" class="rc-button rc-button--secondary setup-wizard-forms__footer-back"> |
|
||||||
<span>{{_ "Back"}}</span> |
|
||||||
</button> |
|
||||||
{{/if}} |
|
||||||
<button type="submit" class="rc-button rc-button--primary setup-wizard-forms__footer-next" disabled={{isContinueDisabled}}> |
|
||||||
<span>{{_ "Continue"}}</span> |
|
||||||
</button> |
|
||||||
</footer> |
|
||||||
</form> |
|
||||||
</div> |
|
||||||
</section> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="setupWizardInfo"> |
|
||||||
<section class="setup-wizard-info"> |
|
||||||
<header class="setup-wizard-info__header"> |
|
||||||
<img class="setup-wizard-info__header-logo" src="images/logo/logo.svg"> |
|
||||||
<span class="setup-wizard-info__header-tag">{{_ "Setup Wizard"}}</span> |
|
||||||
</header> |
|
||||||
<div class="setup-wizard-info__content"> |
|
||||||
<h1 class="setup-wizard-info__content-title">{{_ "Setup_Wizard"}}</h1> |
|
||||||
<p class="setup-wizard-info__content-text">{{_ "Setup_Wizard_Info"}}</p> |
|
||||||
<ol class="setup-wizard-info__steps"> |
|
||||||
<li class="setup-wizard-info__steps-item {{stepItemModifier 1}}">{{_ (stepTitle 1)}}</li> |
|
||||||
<li class="setup-wizard-info__steps-item {{stepItemModifier 2}}">{{_ (stepTitle 2)}}<span class="setup-wizard-info__steps-item-bonding"></span></li> |
|
||||||
<li class="setup-wizard-info__steps-item {{stepItemModifier 3}}">{{_ (stepTitle 3)}}<span class="setup-wizard-info__steps-item-bonding"></span></li> |
|
||||||
<li class="setup-wizard-info__steps-item {{stepItemModifier 4}}">{{_ (stepTitle 4)}}<span class="setup-wizard-info__steps-item-bonding"></span></li> |
|
||||||
</ol> |
|
||||||
</div> |
|
||||||
</section> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="setupWizardAdminInfo"> |
|
||||||
<div class="setup-wizard-forms__content-step {{#if $eq currentStep 1}}setup-wizard-forms__content-step--active{{/if}}"> |
|
||||||
<div class="rc-input"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ "Name"}}</div> |
|
||||||
<div class="rc-input__wrapper"> |
|
||||||
<div class="rc-input__icon"> |
|
||||||
{{> icon block="rc-input__icon-sv" icon="user"}} |
|
||||||
</div> |
|
||||||
<input type="text" class="rc-input__element js-setting-data" name="registration-name" placeholder="{{_ 'Type_your_name'}}" value="{{name}}"> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
<div class="rc-input {{#if invalidUsername}}rc-input--error{{/if}}"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ "Username"}}</div> |
|
||||||
<div class="rc-input__wrapper"> |
|
||||||
<div class="rc-input__icon"> |
|
||||||
{{> icon block="rc-input__icon-sv" icon="at"}} |
|
||||||
</div> |
|
||||||
<input type="text" class="rc-input__element js-setting-data" name="registration-username" placeholder="{{_ 'Type_your_username'}}" value="{{username}}"> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
{{#if invalidUsername}} |
|
||||||
<div class="rc-input__error"> |
|
||||||
<div class="rc-input__error-icon"> |
|
||||||
{{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} |
|
||||||
</div> |
|
||||||
<div class="rc-input__error-message">{{_ "Invalid_username"}}</div> |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
</div> |
|
||||||
<div class="rc-input {{#if invalidEmail}}rc-input--error{{/if}}"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ "Organization_Email"}}</div> |
|
||||||
<div class="rc-input__wrapper"> |
|
||||||
<div class="rc-input__icon"> |
|
||||||
{{> icon block="rc-input__icon-sv" icon="mail"}} |
|
||||||
</div> |
|
||||||
<input type="email" class="rc-input__element js-setting-data" name="registration-email" placeholder="{{_ 'Type_your_email'}}" value="{{email}}"> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
{{#if invalidEmail}} |
|
||||||
<div class="rc-input__error"> |
|
||||||
<div class="rc-input__error-icon"> |
|
||||||
{{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} |
|
||||||
</div> |
|
||||||
<div class="rc-input__error-message">{{_ "Invalid_email"}}</div> |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
</div> |
|
||||||
<div class="rc-input"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ "Password"}}</div> |
|
||||||
<div class="rc-input__wrapper"> |
|
||||||
<div class="rc-input__icon"> |
|
||||||
{{> icon block="rc-input__icon-sv" icon="key"}} |
|
||||||
</div> |
|
||||||
<input type="password" class="rc-input__element js-setting-data" name="registration-pass" placeholder="{{_ 'Type_your_password'}}" value="{{password}}"> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="setupWizardRegisterServer"> |
|
||||||
<div class="setup-wizard-forms__content-step {{#if $eq currentStep 4}}setup-wizard-forms__content-step--active{{/if}}"> |
|
||||||
<p class="setup-wizard-forms__content-text">{{_ "Register_Server_Info"}}</p> |
|
||||||
<form class="setup-wizard-forms__content-register"> |
|
||||||
<label class="setup-wizard-forms__content-register-option {{#if registerServer}}setup-wizard-forms__content-register-option--selected{{/if}}"> |
|
||||||
<div class="setup-wizard-forms__content-register-radio"> |
|
||||||
<input type="radio" name="registerServer" value="true" class="setup-wizard-forms__content-register-radio-element" checked="{{registerServer}}"> |
|
||||||
<span class="setup-wizard-forms__content-register-radio-fake"></span> |
|
||||||
<span class="setup-wizard-forms__content-register-radio-text">{{_ "Register_Server_Registered"}}</span> |
|
||||||
</div> |
|
||||||
<ul class="setup-wizard-forms__content-register-items"> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="check"}}{{_ "Register_Server_Registered_Push_Notifications"}}</li> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="check"}}{{_ "Register_Server_Registered_Livechat"}}</li> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="check"}}{{_ "Register_Server_Registered_OAuth"}}</li> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="check"}}{{_ "Register_Server_Registered_Marketplace"}}</li> |
|
||||||
</ul> |
|
||||||
<div> |
|
||||||
<label class="setup-wizard-forms__content-register-checkbox"> |
|
||||||
<input type="checkbox" name="optIn" value="true" class="setup-wizard-forms__content-register-checkbox-element" checked="{{optIn}}" disabled={{$not registerServer}}> |
|
||||||
<span class="setup-wizard-forms__content-register-checkbox-fake"> |
|
||||||
{{> icon block="setup-wizard-forms__content-register-checkbox-fake-icon" icon="check"}} |
|
||||||
</span> |
|
||||||
<span class="setup-wizard-forms__content-register-checkbox-text">{{_ "Register_Server_Opt_In"}}</span> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
<label class="setup-wizard-forms__content-register-option {{#if $not registerServer}}setup-wizard-forms__content-register-option--selected{{/if}} {{#if $not allowStandaloneServer}}setup-wizard-forms__content-register-option--disabled{{/if}}"> |
|
||||||
<div class="setup-wizard-forms__content-register-radio"> |
|
||||||
<input type="radio" name="registerServer" value="false" class="setup-wizard-forms__content-register-radio-element" checked="{{$not registerServer}}" disabled={{$not allowStandaloneServer}}> |
|
||||||
<span class="setup-wizard-forms__content-register-radio-fake"></span> |
|
||||||
<span class="setup-wizard-forms__content-register-radio-text">{{_ "Register_Server_Standalone"}}</span> |
|
||||||
</div> |
|
||||||
<ul class="setup-wizard-forms__content-register-items"> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="circle"}}{{_ "Register_Server_Standalone_Service_Providers"}}</li> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="circle"}}{{_ "Register_Server_Standalone_Update_Settings"}}</li> |
|
||||||
<li class="setup-wizard-forms__content-register-item">{{> icon block="setup-wizard-forms__content-register-radio-icon" icon="circle"}}{{_ "Register_Server_Standalone_Own_Certificates"}}</li> |
|
||||||
</ul> |
|
||||||
</label> |
|
||||||
</form> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="setupWizardCustomStep"> |
|
||||||
<div class="setup-wizard-forms__content-step {{#if $eq currentStep step}}setup-wizard-forms__content-step--active{{/if}}"> |
|
||||||
{{#each settings}} |
|
||||||
{{#if $eq type 'string'}} |
|
||||||
<div class="rc-input"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ label}}</div> |
|
||||||
<div class="rc-input__wrapper"> |
|
||||||
<input type="text" class="rc-input__element js-setting-data" name="{{id}}" value="{{value}}"> |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
|
|
||||||
{{#if $eq type 'select'}} |
|
||||||
<div class="rc-input"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ label}}</div> |
|
||||||
<div class="rc-select"> |
|
||||||
<select class="rc-select__element js-setting-data" name="{{id}}"> |
|
||||||
<option value="" disabled selected="{{this.isValueSelected undefined}}">{{_ "Select_an_option"}}</option> |
|
||||||
{{#each options}} |
|
||||||
<option class="rc-select__option" value="{{optionValue}}" selected="{{this.isValueSelected optionValue}}">{{_ optionLabel}}</option> |
|
||||||
{{/each}} |
|
||||||
</select> |
|
||||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
|
|
||||||
{{#if $eq type 'language'}} |
|
||||||
<div class="rc-input"> |
|
||||||
<label class="rc-input__label"> |
|
||||||
<div class="rc-input__title">{{_ label}}</div> |
|
||||||
<div class="rc-select"> |
|
||||||
<select class="rc-select__element js-setting-data" name="{{id}}"> |
|
||||||
<option value="" disabled selected="{{this.isValueSelected undefined}}">{{_ "Default"}}</option> |
|
||||||
{{#each options}} |
|
||||||
<option class="rc-select__option" value="{{optionValue}}" selected="{{this.isValueSelected optionValue}}" dir="auto">{{_ optionLabel}}</option> |
|
||||||
{{/each}} |
|
||||||
</select> |
|
||||||
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
|
||||||
</div> |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
{{/each}} |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
@ -1 +0,0 @@ |
|||||||
import './getSetupWizardParameters'; |
|
||||||
@ -1,574 +0,0 @@ |
|||||||
.setup-wizard { |
|
||||||
--step-color: var(--rc-color-button-primary); |
|
||||||
--highlight-color: var(--rc-color-button-primary); |
|
||||||
|
|
||||||
display: flex; |
|
||||||
|
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
|
|
||||||
background-color: #f7f8fa; |
|
||||||
|
|
||||||
justify-content: center; |
|
||||||
|
|
||||||
&-info { |
|
||||||
|
|
||||||
overflow: hidden; |
|
||||||
flex: 0 1 350px; |
|
||||||
|
|
||||||
margin: 55px 65px 30px 80px; |
|
||||||
|
|
||||||
&__header { |
|
||||||
display: flex; |
|
||||||
|
|
||||||
margin: 0 -0.375rem 3rem; |
|
||||||
align-items: center; |
|
||||||
|
|
||||||
&-logo { |
|
||||||
height: 1.5rem; |
|
||||||
margin: 0 0.375rem; |
|
||||||
} |
|
||||||
|
|
||||||
&-tag { |
|
||||||
margin: 0 0.375rem; |
|
||||||
|
|
||||||
padding: 4px 8px; |
|
||||||
|
|
||||||
letter-spacing: 0.05rem; |
|
||||||
|
|
||||||
text-transform: uppercase; |
|
||||||
|
|
||||||
color: #ffffff; |
|
||||||
|
|
||||||
border-radius: 50px; |
|
||||||
background: #2f343d; |
|
||||||
|
|
||||||
font-size: 10px; |
|
||||||
|
|
||||||
line-height: 1rem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__content { |
|
||||||
&-title { |
|
||||||
margin-bottom: 1rem; |
|
||||||
|
|
||||||
letter-spacing: 0.03rem; |
|
||||||
|
|
||||||
color: #2f343d; |
|
||||||
|
|
||||||
font-size: 2rem; |
|
||||||
|
|
||||||
font-weight: 600; |
|
||||||
|
|
||||||
line-height: 2.6rem; |
|
||||||
} |
|
||||||
|
|
||||||
&-text { |
|
||||||
margin-bottom: 3rem; |
|
||||||
|
|
||||||
color: #9ea2a8; |
|
||||||
|
|
||||||
font-size: 1rem; |
|
||||||
|
|
||||||
font-weight: 500; |
|
||||||
|
|
||||||
line-height: 1.5rem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__steps { |
|
||||||
counter-reset: steps; |
|
||||||
|
|
||||||
&-item { |
|
||||||
position: relative; |
|
||||||
|
|
||||||
margin: 0 -0.5rem; |
|
||||||
|
|
||||||
counter-increment: steps; |
|
||||||
|
|
||||||
color: #d3d5d9; |
|
||||||
|
|
||||||
font-size: 0.875rem; |
|
||||||
font-weight: 500; |
|
||||||
|
|
||||||
&:not(:last-child) { |
|
||||||
margin-bottom: 2rem; |
|
||||||
|
|
||||||
&::after { |
|
||||||
position: absolute; |
|
||||||
bottom: -1rem; |
|
||||||
left: 1.2rem; |
|
||||||
|
|
||||||
display: block; |
|
||||||
|
|
||||||
width: 1px; |
|
||||||
height: 1rem; |
|
||||||
|
|
||||||
content: ""; |
|
||||||
|
|
||||||
background-color: #d3d5d9; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&::before { |
|
||||||
display: inline-flex; |
|
||||||
|
|
||||||
width: 1.5rem; |
|
||||||
height: 1.5rem; |
|
||||||
margin: 0 0.5rem; |
|
||||||
|
|
||||||
content: counter(steps); |
|
||||||
|
|
||||||
color: #d3d5d9; |
|
||||||
|
|
||||||
border: 1px solid #d3d5d9; |
|
||||||
border-radius: 50px; |
|
||||||
|
|
||||||
font-size: 0.75rem; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
} |
|
||||||
|
|
||||||
&--active { |
|
||||||
color: var(--rc-color-button-primary); |
|
||||||
|
|
||||||
&::before { |
|
||||||
color: var(--rc-color-button-primary); |
|
||||||
border-color: var(--rc-color-button-primary); |
|
||||||
background-color: transparent; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&--past { |
|
||||||
color: var(--rc-color-primary); |
|
||||||
|
|
||||||
&::before { |
|
||||||
color: var(--rc-color-content); |
|
||||||
border-color: var(--rc-color-button-primary); |
|
||||||
background-color: var(--rc-color-button-primary); |
|
||||||
} |
|
||||||
|
|
||||||
&::after { |
|
||||||
background-color: var(--rc-color-button-primary) !important; |
|
||||||
} |
|
||||||
|
|
||||||
& .setup-wizard-info__steps-item-bonding { |
|
||||||
background-color: var(--rc-color-button-primary); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-bonding { |
|
||||||
position: absolute; |
|
||||||
top: -1rem; |
|
||||||
left: 1.2rem; |
|
||||||
|
|
||||||
display: block; |
|
||||||
|
|
||||||
width: 1px; |
|
||||||
height: 1rem; |
|
||||||
|
|
||||||
background-color: currentColor; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-forms { |
|
||||||
flex: 1 1 auto; |
|
||||||
|
|
||||||
&__wrapper { |
|
||||||
display: flex; |
|
||||||
overflow-y: auto; |
|
||||||
|
|
||||||
width: calc(100% - 1rem); |
|
||||||
height: calc(100% - 2rem); |
|
||||||
margin: 1rem 1rem 1rem 0; |
|
||||||
|
|
||||||
border-radius: 2px; |
|
||||||
background: #ffffff; |
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); |
|
||||||
justify-content: center; |
|
||||||
} |
|
||||||
|
|
||||||
&__box { |
|
||||||
display: flex; |
|
||||||
|
|
||||||
visibility: hidden; |
|
||||||
flex-direction: column; |
|
||||||
|
|
||||||
width: 350px; |
|
||||||
min-height: min-content; |
|
||||||
margin: 3rem; |
|
||||||
|
|
||||||
transition: opacity 1s linear; |
|
||||||
|
|
||||||
opacity: 0; |
|
||||||
|
|
||||||
&--loaded { |
|
||||||
visibility: visible; |
|
||||||
|
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__header { |
|
||||||
margin-bottom: 2rem; |
|
||||||
|
|
||||||
&-step { |
|
||||||
display: block; |
|
||||||
|
|
||||||
margin-bottom: 3px; |
|
||||||
|
|
||||||
letter-spacing: 0.05rem; |
|
||||||
|
|
||||||
text-transform: uppercase; |
|
||||||
|
|
||||||
color: #caced1; |
|
||||||
|
|
||||||
font-size: 0.75rem; |
|
||||||
|
|
||||||
line-height: 1.125rem; |
|
||||||
} |
|
||||||
|
|
||||||
&-title { |
|
||||||
letter-spacing: 0.05rem; |
|
||||||
|
|
||||||
color: #1f2329; |
|
||||||
|
|
||||||
font-size: 1.25rem; |
|
||||||
font-weight: 500; |
|
||||||
|
|
||||||
line-height: 1.75rem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__content { |
|
||||||
margin-bottom: 2rem; |
|
||||||
|
|
||||||
&-step { |
|
||||||
display: none; |
|
||||||
|
|
||||||
&--active { |
|
||||||
display: block; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-text { |
|
||||||
|
|
||||||
margin-bottom: 2rem; |
|
||||||
|
|
||||||
color: #9ea2a8; |
|
||||||
|
|
||||||
font-size: 1rem; |
|
||||||
font-weight: 500; |
|
||||||
|
|
||||||
line-height: 1.5rem; |
|
||||||
} |
|
||||||
|
|
||||||
&-register { |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
|
|
||||||
&-option { |
|
||||||
|
|
||||||
display: block; |
|
||||||
|
|
||||||
padding: 1.5rem; |
|
||||||
|
|
||||||
cursor: pointer; |
|
||||||
|
|
||||||
color: #2f343d; |
|
||||||
border: 2px solid #e7ebf2; |
|
||||||
|
|
||||||
border-radius: 2px; |
|
||||||
|
|
||||||
font-size: 0.875rem; |
|
||||||
|
|
||||||
line-height: 1.25rem; |
|
||||||
|
|
||||||
&--selected { |
|
||||||
border-color: var(--highlight-color); |
|
||||||
} |
|
||||||
|
|
||||||
&--disabled { |
|
||||||
opacity: 0.25; |
|
||||||
} |
|
||||||
|
|
||||||
&:first-child { |
|
||||||
margin-bottom: 1rem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-radio { |
|
||||||
position: relative; |
|
||||||
|
|
||||||
display: flex; |
|
||||||
|
|
||||||
margin: 0 -0.5rem 1rem; |
|
||||||
|
|
||||||
&-element { |
|
||||||
position: absolute; |
|
||||||
z-index: -1; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
|
|
||||||
width: 0; |
|
||||||
height: 0; |
|
||||||
|
|
||||||
&:checked + .setup-wizard-forms__content-register-radio-fake { |
|
||||||
|
|
||||||
position: relative; |
|
||||||
|
|
||||||
border-color: var(--highlight-color); |
|
||||||
|
|
||||||
&::before { |
|
||||||
|
|
||||||
position: absolute; |
|
||||||
|
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
|
|
||||||
content: ""; |
|
||||||
|
|
||||||
border: 2px solid transparent; |
|
||||||
border-radius: 50%; |
|
||||||
|
|
||||||
background-color: var(--highlight-color); |
|
||||||
|
|
||||||
background-clip: padding-box; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-fake { |
|
||||||
display: block; |
|
||||||
|
|
||||||
width: 20px; |
|
||||||
height: 20px; |
|
||||||
margin: 0 0.5rem; |
|
||||||
|
|
||||||
border: 2px solid #cfd8e6; |
|
||||||
border-radius: 50px; |
|
||||||
} |
|
||||||
|
|
||||||
&-text { |
|
||||||
font-weight: 500; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-checkbox { |
|
||||||
position: relative; |
|
||||||
|
|
||||||
display: flex; |
|
||||||
|
|
||||||
margin: 0 -0.5rem 1rem; |
|
||||||
|
|
||||||
cursor: inherit; |
|
||||||
|
|
||||||
&-element { |
|
||||||
position: absolute; |
|
||||||
z-index: -1; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
|
|
||||||
width: 0; |
|
||||||
height: 0; |
|
||||||
|
|
||||||
&:checked + .setup-wizard-forms__content-register-checkbox-fake { |
|
||||||
position: relative; |
|
||||||
|
|
||||||
color: var(--rc-color-content); |
|
||||||
|
|
||||||
border-color: var(--highlight-color); |
|
||||||
background-color: var(--highlight-color); |
|
||||||
|
|
||||||
.setup-wizard-forms__content-register-checkbox-fake-icon { |
|
||||||
display: block; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-fake { |
|
||||||
display: block; |
|
||||||
|
|
||||||
width: 16px; |
|
||||||
height: 16px; |
|
||||||
margin: 2px 0.5rem; |
|
||||||
|
|
||||||
border: 2px solid #cfd8e6; |
|
||||||
border-radius: 2px; |
|
||||||
|
|
||||||
&-icon { |
|
||||||
|
|
||||||
display: none; |
|
||||||
|
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-text { |
|
||||||
color: #666666; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-items + * { |
|
||||||
margin-top: 1rem; |
|
||||||
} |
|
||||||
|
|
||||||
&-item { |
|
||||||
|
|
||||||
display: flex; |
|
||||||
|
|
||||||
margin: 0 -0.5rem 0.5rem; |
|
||||||
align-items: center; |
|
||||||
|
|
||||||
&:last-child { |
|
||||||
margin-bottom: 0; |
|
||||||
} |
|
||||||
|
|
||||||
& .setup-wizard-forms__content-register-radio-icon { |
|
||||||
width: 20px; |
|
||||||
min-width: 20px; |
|
||||||
height: 20px; |
|
||||||
margin: 0 0.5rem; |
|
||||||
align-self: baseline; |
|
||||||
|
|
||||||
&--check { |
|
||||||
color: var(--highlight-color); |
|
||||||
} |
|
||||||
|
|
||||||
&--circle { |
|
||||||
height: 6px; |
|
||||||
margin: 7px 0.5rem; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__footer { |
|
||||||
display: flex; |
|
||||||
flex-direction: row; |
|
||||||
|
|
||||||
margin: 0 -0.5rem 2rem; |
|
||||||
|
|
||||||
& .rc-button { |
|
||||||
margin: 0 0.5rem; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&-final { |
|
||||||
width: 930px; |
|
||||||
padding-top: 5rem; |
|
||||||
|
|
||||||
&__header { |
|
||||||
margin-bottom: 5rem; |
|
||||||
} |
|
||||||
|
|
||||||
&__box { |
|
||||||
padding: 5rem 6rem; |
|
||||||
|
|
||||||
border-radius: 2px; |
|
||||||
background: #ffffff; |
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); |
|
||||||
|
|
||||||
&-title { |
|
||||||
margin-bottom: 3.25rem; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
&__link { |
|
||||||
display: block; |
|
||||||
|
|
||||||
margin-bottom: 1.5rem; |
|
||||||
|
|
||||||
letter-spacing: 0; |
|
||||||
|
|
||||||
color: var(--highlight-color); |
|
||||||
|
|
||||||
font-size: 1rem; |
|
||||||
|
|
||||||
line-height: 1.5rem; |
|
||||||
|
|
||||||
&-text { |
|
||||||
display: block; |
|
||||||
|
|
||||||
letter-spacing: 0.03rem; |
|
||||||
|
|
||||||
text-transform: uppercase; |
|
||||||
|
|
||||||
color: #2f343d; |
|
||||||
|
|
||||||
font-size: 0.625rem; |
|
||||||
line-height: 1rem; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
& .rc-input { |
|
||||||
&:not(:last-child) { |
|
||||||
margin-bottom: 1.5rem; |
|
||||||
} |
|
||||||
|
|
||||||
&__title { |
|
||||||
letter-spacing: 0.03rem; |
|
||||||
|
|
||||||
text-transform: uppercase; |
|
||||||
|
|
||||||
color: #9ea2a8; |
|
||||||
|
|
||||||
font-size: 0.625rem; |
|
||||||
|
|
||||||
line-height: 1rem; |
|
||||||
} |
|
||||||
|
|
||||||
&__element { |
|
||||||
color: #030c1a; |
|
||||||
|
|
||||||
font-weight: 500; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
& .rc-select { |
|
||||||
&__element { |
|
||||||
color: #030c1a; |
|
||||||
|
|
||||||
font-size: 0.875rem; |
|
||||||
|
|
||||||
font-weight: 500; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.rtl { |
|
||||||
& .setup-wizard-info { |
|
||||||
margin: 55px 80px 0 65px; |
|
||||||
} |
|
||||||
|
|
||||||
& .setup-wizard-forms__wrapper { |
|
||||||
margin: 1rem 0 1rem 1rem; |
|
||||||
} |
|
||||||
|
|
||||||
& .setup-wizard-info__steps-item { |
|
||||||
&:not(:last-child)::after, |
|
||||||
&-bonding { |
|
||||||
right: 1.2rem; |
|
||||||
left: auto; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@media (width <= 760px) { |
|
||||||
.setup-wizard { |
|
||||||
flex-direction: column; |
|
||||||
justify-content: initial; |
|
||||||
|
|
||||||
& .setup-wizard-forms__wrapper { |
|
||||||
width: 100%; |
|
||||||
margin: 0; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
<template name="fxOsInstallPrompt"> |
|
||||||
<section class="rc-old full-page color-tertiary-font-color"> |
|
||||||
<div class="wrapper"> |
|
||||||
<header> |
|
||||||
<a class="logo" href="/"> |
|
||||||
<img src="images/logo/logo.svg?v=3" /> |
|
||||||
</a> |
|
||||||
</header> |
|
||||||
<div class="cms-page content-background-color"> |
|
||||||
<h1>{{_ "Install_FxOs"}}</h1> |
|
||||||
<p>{{_ "Install_FxOs_follow_instructions"}}</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</section> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="fxOsInstallDone"> |
|
||||||
<section class="rc-old full-page color-tertiary-font-color"> |
|
||||||
<div class="wrapper"> |
|
||||||
<header> |
|
||||||
<a class="logo" href="/"> |
|
||||||
<img src="images/logo/logo.svg?v=3" /> |
|
||||||
</a> |
|
||||||
</header> |
|
||||||
<div class="cms-page content-background-color"> |
|
||||||
<h1>{{_ "Install_FxOs"}}</h1> |
|
||||||
<p>{{_ "Install_FxOs_done"}}</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</section> |
|
||||||
</template> |
|
||||||
|
|
||||||
<template name="fxOsInstallError"> |
|
||||||
<section class="rc-old full-page color-tertiary-font-color"> |
|
||||||
<div class="wrapper"> |
|
||||||
<header> |
|
||||||
<a class="logo" href="/"> |
|
||||||
<img src="images/logo/logo.svg?v=3" /> |
|
||||||
</a> |
|
||||||
</header> |
|
||||||
<div class="cms-page content-background-color"> |
|
||||||
<h1>{{_ "Install_FxOs"}}</h1> |
|
||||||
<p>{{_ "Install_FxOs_error"}}</p> |
|
||||||
<p>{{installError}}</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</section> |
|
||||||
</template> |
|
||||||
@ -1,21 +0,0 @@ |
|||||||
import { BlazeLayout } from 'meteor/kadira:blaze-layout'; |
|
||||||
import { Template } from 'meteor/templating'; |
|
||||||
|
|
||||||
Template.fxOsInstallPrompt.onRendered(function() { |
|
||||||
const showPrompt = function() { |
|
||||||
const request = window.navigator.mozApps.install(`http://${ location.host }/manifest.webapp`); |
|
||||||
request.onsuccess = function() { |
|
||||||
BlazeLayout.render('fxOsInstallDone'); |
|
||||||
}; |
|
||||||
request.onerror = function() { |
|
||||||
BlazeLayout.render('fxOsInstallError', { |
|
||||||
installError: this.error.name, |
|
||||||
}); |
|
||||||
}; |
|
||||||
}; |
|
||||||
setTimeout(showPrompt, 2000); |
|
||||||
return $('#initial-page-loading').remove(); |
|
||||||
}); |
|
||||||
|
|
||||||
Template.fxOsInstallDone.onRendered(() => $('#initial-page-loading').remove()); |
|
||||||
Template.fxOsInstallError.onRendered(() => $('#initial-page-loading').remove()); |
|
||||||
@ -0,0 +1,14 @@ |
|||||||
|
@font-face { |
||||||
|
font-family: 'RocketChat'; |
||||||
|
font-weight: 400; |
||||||
|
font-style: normal; |
||||||
|
font-display: auto; |
||||||
|
|
||||||
|
src: url('/fonts/RocketChat.eot'); |
||||||
|
src: |
||||||
|
url('/fonts/RocketChat.eot?#iefix') format('embedded-opentype'), |
||||||
|
url('/fonts/RocketChat.woff2') format('woff2'), |
||||||
|
url('/fonts/RocketChat.woff') format('woff'), |
||||||
|
url('/fonts/RocketChat.ttf') format('truetype'), |
||||||
|
url('/fonts/RocketChat.svg#RocketChat') format('svg'); |
||||||
|
} |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
export const Button = ({ |
||||||
|
children, |
||||||
|
className, |
||||||
|
invisible, |
||||||
|
primary, |
||||||
|
secondary, |
||||||
|
submit, |
||||||
|
...props |
||||||
|
}) => <button |
||||||
|
type={(submit && 'submit') || 'button'} |
||||||
|
className={[ |
||||||
|
'rc-button', |
||||||
|
primary && 'rc-button--primary', |
||||||
|
secondary && 'rc-button--secondary', |
||||||
|
invisible && 'rc-button--invisible', |
||||||
|
className, |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</button>; |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
export const Icon = ({ icon, block = '', baseUrl = '', className }) => <svg |
||||||
|
className={[ |
||||||
|
'rc-icon', |
||||||
|
block, |
||||||
|
block && icon && `${ block }--${ icon }`, |
||||||
|
className, |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
aria-hidden='true' |
||||||
|
> |
||||||
|
<use xlinkHref={`${ baseUrl }#icon-${ icon }`} /> |
||||||
|
</svg>; |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
import React, { useEffect, useRef } from 'react'; |
||||||
|
|
||||||
|
import { Icon } from './Icon'; |
||||||
|
|
||||||
|
export const Input = ({ |
||||||
|
error, |
||||||
|
title, |
||||||
|
icon, |
||||||
|
type = 'text', |
||||||
|
className, |
||||||
|
placeholder, |
||||||
|
options, |
||||||
|
focused, |
||||||
|
...props |
||||||
|
}) => { |
||||||
|
const ref = useRef(null); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (focused && ref.current) { |
||||||
|
ref.current.focus(); |
||||||
|
} |
||||||
|
}, [focused]); |
||||||
|
|
||||||
|
return <div |
||||||
|
className={[ |
||||||
|
'rc-input', |
||||||
|
error && 'rc-input--error', |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
> |
||||||
|
<label className='rc-input__label'> |
||||||
|
{title && <div className='rc-input__title'>{title}</div>} |
||||||
|
{['text', 'email', 'password'].includes(type) && <div className='rc-input__wrapper'> |
||||||
|
{icon && <div className='rc-input__icon'> |
||||||
|
<Icon block='rc-input__icon-sv' icon={icon} /> |
||||||
|
</div>}<input |
||||||
|
type={type} |
||||||
|
className={['rc-input__element', className].filter(Boolean).join(' ')} |
||||||
|
placeholder={placeholder} |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
</div>} |
||||||
|
{type === 'select' && <div className='rc-select'> |
||||||
|
<select |
||||||
|
className={['rc-select__element', className].filter(Boolean).join(' ')} |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{placeholder && <option |
||||||
|
disabled |
||||||
|
value='' |
||||||
|
>{placeholder}</option>} |
||||||
|
{options.map(({ label, value }, i) => |
||||||
|
<option key={i} className='rc-select__option' value={value}>{label}</option> |
||||||
|
)} |
||||||
|
</select> |
||||||
|
<Icon block='rc-select__arrow' icon='arrow-down' /> |
||||||
|
</div>} |
||||||
|
{typeof error === 'string' && error && <div className='rc-input__error'> |
||||||
|
<div className='rc-input__error-icon'> |
||||||
|
<Icon block='rc-input__error-icon' icon='warning' className='rc-input__error-icon-svg'/> |
||||||
|
</div> |
||||||
|
<div className='rc-input__error-message'>{error}</div> |
||||||
|
</div>} |
||||||
|
</label> |
||||||
|
</div>; |
||||||
|
}; |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
.ConnectionStatusAlert { |
||||||
|
|
||||||
|
position: fixed; |
||||||
|
z-index: 1000000; |
||||||
|
top: 0; |
||||||
|
|
||||||
|
width: 100%; |
||||||
|
padding: 2px; |
||||||
|
|
||||||
|
text-align: center; |
||||||
|
|
||||||
|
color: #916302; |
||||||
|
border-bottom-width: 1px; |
||||||
|
background-color: #fffdf9; |
||||||
|
} |
||||||
|
|
||||||
|
.ConnectionStatusAlert__link { |
||||||
|
color: var(--color-blue); |
||||||
|
} |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import React, { useEffect, useRef, useState } from 'react'; |
||||||
|
|
||||||
|
import { useReactiveValue } from '../../hooks/useReactiveValue'; |
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import { Icon } from '../basic/Icon'; |
||||||
|
|
||||||
|
export function ConnectionStatusAlert() { |
||||||
|
const { |
||||||
|
connected, |
||||||
|
retryTime, |
||||||
|
status, |
||||||
|
} = useReactiveValue(() => ({ ...Meteor.status() })); |
||||||
|
const reconnectionTimerRef = useRef(); |
||||||
|
const [reconnectCountdown, setReconnectCountdown] = useState(0); |
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (status === 'waiting') { |
||||||
|
if (reconnectionTimerRef.current) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
reconnectionTimerRef.current = setInterval(() => { |
||||||
|
const timeDiff = retryTime - Date.now(); |
||||||
|
setReconnectCountdown((timeDiff > 0 && Math.round(timeDiff / 1000)) || 0); |
||||||
|
}, 500); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
clearInterval(reconnectionTimerRef.current); |
||||||
|
reconnectionTimerRef.current = null; |
||||||
|
}, [retryTime, status]); |
||||||
|
|
||||||
|
useEffect(() => () => { |
||||||
|
clearInterval(reconnectionTimerRef.current); |
||||||
|
}, []); |
||||||
|
|
||||||
|
if (connected) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
const handleRetryClick = (event) => { |
||||||
|
event.preventDefault(); |
||||||
|
Meteor.reconnect(); |
||||||
|
}; |
||||||
|
|
||||||
|
return <div className='ConnectionStatusAlert' role='alert'> |
||||||
|
<strong> |
||||||
|
<Icon icon='warning' /> {t('meteor_status', { context: status })} |
||||||
|
</strong> |
||||||
|
|
||||||
|
{status === 'waiting' && <> |
||||||
|
{' '} |
||||||
|
{t('meteor_status_reconnect_in', { count: reconnectCountdown })} |
||||||
|
</>} |
||||||
|
|
||||||
|
{['waiting', 'offline'].includes(status) && <> |
||||||
|
{' '} |
||||||
|
<a |
||||||
|
href='#' |
||||||
|
className='ConnectionStatusAlert__link' |
||||||
|
onClick={handleRetryClick} |
||||||
|
> |
||||||
|
{t('meteor_status_try_now', { context: status })} |
||||||
|
</a> |
||||||
|
</>} |
||||||
|
</div>; |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
.PageNotFound { |
||||||
|
width: 100%; |
||||||
|
min-height: 100vh; |
||||||
|
padding: 10%; |
||||||
|
|
||||||
|
text-align: center; |
||||||
|
|
||||||
|
color: white; |
||||||
|
background-color: var(--rc-color-primary); |
||||||
|
background-image: url('/images/404.svg'); |
||||||
|
background-repeat: no-repeat; |
||||||
|
background-position: center; |
||||||
|
background-size: cover; |
||||||
|
} |
||||||
|
|
||||||
|
.PageNotFound__404 { |
||||||
|
display: block; |
||||||
|
|
||||||
|
padding: 10px; |
||||||
|
|
||||||
|
font-size: 4em; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.PageNotFound__message { |
||||||
|
display: block; |
||||||
|
|
||||||
|
padding: 10px; |
||||||
|
|
||||||
|
font-size: 2em; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.PageNotFound__description { |
||||||
|
display: block; |
||||||
|
|
||||||
|
padding: 10px; |
||||||
|
|
||||||
|
font-size: 1em; |
||||||
|
} |
||||||
|
|
||||||
|
.PageNotFound__actions { |
||||||
|
margin: 2rem; |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
import { Button, ButtonGroup } from '@rocket.chat/fuselage'; |
||||||
|
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import { useWipeInitialPageLoading } from '../../hooks/useWipeInitialPageLoading'; |
||||||
|
import { ConnectionStatusAlert } from '../connectionStatus/ConnectionStatusAlert'; |
||||||
|
|
||||||
|
export function PageNotFound() { |
||||||
|
useWipeInitialPageLoading(); |
||||||
|
|
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
const handleGoToPreviousPageClick = () => { |
||||||
|
window.history.back(); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleGoHomeClick = () => { |
||||||
|
FlowRouter.go('home'); |
||||||
|
}; |
||||||
|
|
||||||
|
return <> |
||||||
|
<ConnectionStatusAlert /> |
||||||
|
<section className='PageNotFound'> |
||||||
|
<span className='PageNotFound__404'>404</span> |
||||||
|
<span className='PageNotFound__message'>{t('Oops_page_not_found')}</span> |
||||||
|
<span className='PageNotFound__description'>{t('Sorry_page_you_requested_does_not_exists_or_was_deleted')}</span> |
||||||
|
|
||||||
|
<div className='PageNotFound__actions'> |
||||||
|
<ButtonGroup> |
||||||
|
<Button type='button' primary onClick={handleGoToPreviousPageClick}>{t('Return_to_previous_page')}</Button> |
||||||
|
<Button type='button' primary onClick={handleGoHomeClick}>{t('Return_to_home')}</Button> |
||||||
|
</ButtonGroup> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</>; |
||||||
|
} |
||||||
@ -0,0 +1,81 @@ |
|||||||
|
.SetupWizard__Epilogue { |
||||||
|
width: 930px; |
||||||
|
padding-top: 5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-header { |
||||||
|
display: flex; |
||||||
|
|
||||||
|
margin: 0 -0.375rem 5rem; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-headerLogo { |
||||||
|
height: 1.5rem; |
||||||
|
margin: 0 0.375rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-content { |
||||||
|
padding: 5rem 6rem; |
||||||
|
|
||||||
|
border-radius: 2px; |
||||||
|
background: #ffffff; |
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-runningHead { |
||||||
|
display: block; |
||||||
|
|
||||||
|
margin-bottom: 3px; |
||||||
|
|
||||||
|
letter-spacing: 0.05rem; |
||||||
|
|
||||||
|
text-transform: uppercase; |
||||||
|
|
||||||
|
color: #caced1; |
||||||
|
|
||||||
|
font-size: 0.75rem; |
||||||
|
|
||||||
|
line-height: 1.125rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-title { |
||||||
|
margin-bottom: 3.25rem; |
||||||
|
|
||||||
|
letter-spacing: 0.03rem; |
||||||
|
|
||||||
|
color: #2f343d; |
||||||
|
|
||||||
|
font-size: 2rem; |
||||||
|
|
||||||
|
font-weight: 600; |
||||||
|
|
||||||
|
line-height: 2.6rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-link { |
||||||
|
display: block; |
||||||
|
|
||||||
|
margin-bottom: 1.5rem; |
||||||
|
|
||||||
|
letter-spacing: 0; |
||||||
|
|
||||||
|
color: var(--rc-color-button-primary); |
||||||
|
|
||||||
|
font-size: 1rem; |
||||||
|
|
||||||
|
line-height: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Epilogue-linkLabel { |
||||||
|
display: block; |
||||||
|
|
||||||
|
letter-spacing: 0.03rem; |
||||||
|
|
||||||
|
text-transform: uppercase; |
||||||
|
|
||||||
|
color: var(--color-dark); |
||||||
|
|
||||||
|
font-size: 0.625rem; |
||||||
|
line-height: 1rem; |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Button } from '@rocket.chat/fuselage'; |
||||||
|
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import { useSetting } from '../../hooks/useSetting'; |
||||||
|
import { setSetting } from './functions'; |
||||||
|
import './Epilogue.css'; |
||||||
|
|
||||||
|
export function Epilogue() { |
||||||
|
const t = useTranslation(); |
||||||
|
const siteUrl = useSetting('Site_Url'); |
||||||
|
|
||||||
|
const handleClick = () => { |
||||||
|
setSetting('Show_Setup_Wizard', 'completed'); |
||||||
|
}; |
||||||
|
|
||||||
|
return <section className='SetupWizard__Epilogue'> |
||||||
|
<header className='SetupWizard__Epilogue-header'> |
||||||
|
<img className='SetupWizard__Epilogue-headerLogo' src='images/logo/logo.svg' /> |
||||||
|
</header> |
||||||
|
|
||||||
|
<main className='SetupWizard__Epilogue-content'> |
||||||
|
<span className='SetupWizard__Epilogue-runningHead'>{t('Launched_successfully')}</span> |
||||||
|
<h1 className='SetupWizard__Epilogue-title'>{t('Your_workspace_is_ready')}</h1> |
||||||
|
<span className='SetupWizard__Epilogue-linkLabel'>{t('Your_server_link')}</span> |
||||||
|
<span className='SetupWizard__Epilogue-link'>{siteUrl}</span> |
||||||
|
<Button type='button' primary onClick={handleClick} className='SetupWizard__Epilogue__goToWorkspace'> |
||||||
|
{t('Go_to_your_workspace')} |
||||||
|
</Button> |
||||||
|
</main> |
||||||
|
</section>; |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
import { Button, ButtonGroup } from '@rocket.chat/fuselage'; |
||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
|
||||||
|
export function Pager({ disabled, onBackClick, isContinueEnabled = true }) { |
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
return <ButtonGroup align='start'> |
||||||
|
{onBackClick ? <Button type='button' disabled={disabled} onClick={onBackClick} className='SetupWizard__back'> |
||||||
|
{t('Back')} |
||||||
|
</Button> : null} |
||||||
|
<Button type='submit' primary disabled={!isContinueEnabled || disabled} className='SetupWizard__continue'> |
||||||
|
{t('Continue')} |
||||||
|
</Button> |
||||||
|
</ButtonGroup>; |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; |
||||||
|
|
||||||
|
import { call } from '../../../app/ui-utils/client'; |
||||||
|
|
||||||
|
const ParametersContext = createContext({ |
||||||
|
loaded: true, |
||||||
|
settings: [], |
||||||
|
canDeclineServerRegistration: false, |
||||||
|
}); |
||||||
|
|
||||||
|
export const useSetupWizardParameters = () => useContext(ParametersContext); |
||||||
|
|
||||||
|
export function ParametersProvider({ children }) { |
||||||
|
const [loaded, setLoaded] = useState(false); |
||||||
|
const [settings, setSettings] = useState([]); |
||||||
|
const [canDeclineServerRegistration, setCapableOfDeclineServerRegistration] = useState(false); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const getParameters = async () => { |
||||||
|
const { |
||||||
|
settings, |
||||||
|
allowStandaloneServer, |
||||||
|
} = await call('getSetupWizardParameters') || {}; |
||||||
|
|
||||||
|
setLoaded(true); |
||||||
|
setSettings(settings); |
||||||
|
setCapableOfDeclineServerRegistration(allowStandaloneServer); |
||||||
|
}; |
||||||
|
|
||||||
|
getParameters(); |
||||||
|
}, []); |
||||||
|
|
||||||
|
const value = useMemo(() => ({ |
||||||
|
loaded, |
||||||
|
settings, |
||||||
|
canDeclineServerRegistration, |
||||||
|
}), [loaded, settings, canDeclineServerRegistration]); |
||||||
|
|
||||||
|
return <ParametersContext.Provider value={value}> |
||||||
|
{children} |
||||||
|
</ParametersContext.Provider>; |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
.SetupWizard { |
||||||
|
display: flex; |
||||||
|
|
||||||
|
width: 100%; |
||||||
|
height: 100vh; |
||||||
|
|
||||||
|
background-color: var(--color-dark-05); |
||||||
|
align-items: stretch; |
||||||
|
|
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
@media (width <= 760px) { |
||||||
|
.SetupWizard { |
||||||
|
flex-direction: column; |
||||||
|
justify-content: initial; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useWipeInitialPageLoading } from '../../hooks/useWipeInitialPageLoading'; |
||||||
|
import { ConnectionStatusAlert } from '../connectionStatus/ConnectionStatusAlert'; |
||||||
|
import { ParametersProvider } from './ParametersProvider'; |
||||||
|
import { StateChecker } from './StateChecker'; |
||||||
|
import { Steps } from './Steps'; |
||||||
|
import { StepsState } from './StepsState'; |
||||||
|
import './SetupWizard.css'; |
||||||
|
|
||||||
|
export function SetupWizard() { |
||||||
|
useWipeInitialPageLoading(); |
||||||
|
|
||||||
|
return <> |
||||||
|
<ConnectionStatusAlert /> |
||||||
|
<StateChecker> |
||||||
|
<ParametersProvider> |
||||||
|
<StepsState> |
||||||
|
<div className='SetupWizard'> |
||||||
|
<Steps /> |
||||||
|
</div> |
||||||
|
</StepsState> |
||||||
|
</ParametersProvider> |
||||||
|
</StateChecker> |
||||||
|
</>; |
||||||
|
} |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
.SetupWizard__SideBar { |
||||||
|
display: flex; |
||||||
|
overflow: hidden; |
||||||
|
flex: 0 1 350px; |
||||||
|
flex-flow: column nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-header { |
||||||
|
display: flex; |
||||||
|
|
||||||
|
margin: 2rem 1.625rem; |
||||||
|
align-items: center; |
||||||
|
flex-flow: row wrap; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-headerLogo { |
||||||
|
height: 1.5rem; |
||||||
|
margin: 0.25rem 0.375rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-headerTag { |
||||||
|
margin: 0.25rem 0.375rem; |
||||||
|
|
||||||
|
padding: 4px 8px; |
||||||
|
|
||||||
|
white-space: nowrap; |
||||||
|
|
||||||
|
text-transform: uppercase; |
||||||
|
|
||||||
|
color: var(--color-white); |
||||||
|
|
||||||
|
border-radius: 50px; |
||||||
|
background-color: var(--color-dark); |
||||||
|
|
||||||
|
font-size: 10px; |
||||||
|
|
||||||
|
line-height: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-content { |
||||||
|
overflow: auto; |
||||||
|
flex: 1; |
||||||
|
|
||||||
|
margin: 0 0 1rem; |
||||||
|
padding: 0 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-title { |
||||||
|
margin-bottom: 1rem; |
||||||
|
|
||||||
|
letter-spacing: 0.03rem; |
||||||
|
|
||||||
|
color: var(--color-dark); |
||||||
|
|
||||||
|
font-size: 2rem; |
||||||
|
|
||||||
|
font-weight: 600; |
||||||
|
|
||||||
|
line-height: 2.6rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-text { |
||||||
|
margin-bottom: 3rem; |
||||||
|
|
||||||
|
color: var(--color-gray); |
||||||
|
|
||||||
|
font-size: 1rem; |
||||||
|
|
||||||
|
font-weight: 500; |
||||||
|
|
||||||
|
line-height: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-step { |
||||||
|
position: relative; |
||||||
|
|
||||||
|
margin: 0 -0.5rem 2rem; |
||||||
|
|
||||||
|
color: var(--color-dark-10); |
||||||
|
|
||||||
|
font-size: 0.875rem; |
||||||
|
font-weight: 500; |
||||||
|
|
||||||
|
&::before { |
||||||
|
display: inline-flex; |
||||||
|
|
||||||
|
width: 1.5rem; |
||||||
|
height: 1.5rem; |
||||||
|
margin: 0 0.5rem; |
||||||
|
|
||||||
|
content: attr(data-number); |
||||||
|
|
||||||
|
color: var(--color-dark-10); |
||||||
|
|
||||||
|
border: 1px solid var(--color-dark-10); |
||||||
|
border-radius: 9999px; |
||||||
|
|
||||||
|
font-size: 0.75rem; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
&::after { |
||||||
|
position: absolute; |
||||||
|
bottom: -2rem; |
||||||
|
left: 1.2rem; |
||||||
|
|
||||||
|
display: block; |
||||||
|
|
||||||
|
width: 0.0625rem; |
||||||
|
height: 2rem; |
||||||
|
|
||||||
|
content: ''; |
||||||
|
|
||||||
|
background-color: var(--color-dark-10); |
||||||
|
} |
||||||
|
|
||||||
|
&:last-child { |
||||||
|
margin-bottom: 0; |
||||||
|
|
||||||
|
&::after { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--active { |
||||||
|
color: var(--rc-color-button-primary); |
||||||
|
|
||||||
|
&::before { |
||||||
|
color: var(--rc-color-button-primary); |
||||||
|
border-color: var(--rc-color-button-primary); |
||||||
|
background-color: transparent; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--past { |
||||||
|
color: var(--rc-color-primary); |
||||||
|
|
||||||
|
&::before { |
||||||
|
color: var(--rc-color-content); |
||||||
|
border-color: var(--rc-color-button-primary); |
||||||
|
background-color: var(--rc-color-button-primary); |
||||||
|
} |
||||||
|
|
||||||
|
&::after { |
||||||
|
background-color: var(--rc-color-button-primary); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.rtl &::after { |
||||||
|
right: 1.2rem; |
||||||
|
left: auto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (width <= 760px) { |
||||||
|
.SetupWizard__SideBar { |
||||||
|
flex-basis: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__SideBar-content { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import { useSetupWizardStepsState } from './StepsState'; |
||||||
|
import './SideBar.css'; |
||||||
|
|
||||||
|
export function SideBar({ steps = [] }) { |
||||||
|
const { currentStep } = useSetupWizardStepsState(); |
||||||
|
|
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
return <aside className='SetupWizard__SideBar'> |
||||||
|
<header className='SetupWizard__SideBar-header'> |
||||||
|
<img className='SetupWizard__SideBar-headerLogo' src='images/logo/logo.svg' /> |
||||||
|
<span className='SetupWizard__SideBar-headerTag'>{t('Setup_Wizard')}</span> |
||||||
|
</header> |
||||||
|
|
||||||
|
<div className='SetupWizard__SideBar-content'> |
||||||
|
<h2 className='SetupWizard__SideBar-title'>{t('Setup_Wizard')}</h2> |
||||||
|
<p className='SetupWizard__SideBar-text'>{t('Setup_Wizard_Info')}</p> |
||||||
|
|
||||||
|
<ol className='SetupWizard__SideBar-steps'> |
||||||
|
{steps.map(({ step, title }) => |
||||||
|
<li |
||||||
|
key={step} |
||||||
|
className={[ |
||||||
|
'SetupWizard__SideBar-step', |
||||||
|
step === currentStep && 'SetupWizard__SideBar-step--active', |
||||||
|
step < currentStep && 'SetupWizard__SideBar-step--past', |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
data-number={step} |
||||||
|
> |
||||||
|
{title} |
||||||
|
</li> |
||||||
|
)} |
||||||
|
</ol> |
||||||
|
</div> |
||||||
|
</aside>; |
||||||
|
} |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||||
|
import React, { useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
import { hasRole } from '../../../app/authorization'; |
||||||
|
import { Users } from '../../../app/models'; |
||||||
|
import { useSetting } from '../../hooks/useSetting'; |
||||||
|
import { useUserId } from '../../hooks/useUserId'; |
||||||
|
import { useReactiveValue } from '../../hooks/useReactiveValue'; |
||||||
|
|
||||||
|
export function StateChecker({ children }) { |
||||||
|
const setupWizardState = useSetting('Show_Setup_Wizard'); |
||||||
|
const userId = useUserId(); |
||||||
|
const user = useReactiveValue(() => Users.findOne(userId, { fields: { status: true } }), [userId]); |
||||||
|
|
||||||
|
const [renderAllowed, allowRender] = useState(false); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!setupWizardState) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (userId && (!user || !user.status)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const isComplete = setupWizardState === 'completed'; |
||||||
|
const noUserLoggedInAndIsNotPending = !renderAllowed && !user && setupWizardState !== 'pending'; |
||||||
|
const userIsLoggedInButIsNotAdmin = !!user && !hasRole(user._id, 'admin'); |
||||||
|
|
||||||
|
const mustRedirect = isComplete || noUserLoggedInAndIsNotPending || userIsLoggedInButIsNotAdmin; |
||||||
|
|
||||||
|
if (mustRedirect) { |
||||||
|
FlowRouter.withReplaceState(() => { |
||||||
|
FlowRouter.go('home'); |
||||||
|
}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
allowRender(true); |
||||||
|
}, [setupWizardState, userId, user]); |
||||||
|
|
||||||
|
if (!renderAllowed) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return <>{children}</>; |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
.SetupWizard__Step { |
||||||
|
display: none; |
||||||
|
|
||||||
|
flex-direction: column; |
||||||
|
|
||||||
|
max-width: 350px; |
||||||
|
margin: 2rem auto; |
||||||
|
|
||||||
|
transition: opacity 1s; |
||||||
|
|
||||||
|
opacity: 1; |
||||||
|
|
||||||
|
&--active { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
|
||||||
|
&--working { |
||||||
|
opacity: 0.5; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media (width <= 760px) { |
||||||
|
.SetupWizard__Step { |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import './Step.css'; |
||||||
|
|
||||||
|
export const Step = ({ active, working = false, ...props }) => |
||||||
|
<form |
||||||
|
className={[ |
||||||
|
'SetupWizard__Step', |
||||||
|
active && 'SetupWizard__Step--active', |
||||||
|
working && 'SetupWizard__Step--working', |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
disabled={working} |
||||||
|
{...props} |
||||||
|
/>; |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
.SetupWizard__StepContent { |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import './StepContent.css'; |
||||||
|
|
||||||
|
export function StepContent(props) { |
||||||
|
return <section className='SetupWizard__StepContent' {...props} />; |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
.SetupWizard__StepHeader { |
||||||
|
margin-bottom: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__StepHeader-runningHead { |
||||||
|
margin-bottom: 3px; |
||||||
|
|
||||||
|
letter-spacing: 0.05rem; |
||||||
|
|
||||||
|
text-transform: uppercase; |
||||||
|
|
||||||
|
color: var(--color-dark-20); |
||||||
|
|
||||||
|
font-size: 0.75rem; |
||||||
|
|
||||||
|
line-height: 1.125rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__StepHeader-title { |
||||||
|
letter-spacing: 0.05rem; |
||||||
|
|
||||||
|
color: var(--color-dark-90); |
||||||
|
|
||||||
|
font-size: 1.25rem; |
||||||
|
font-weight: 500; |
||||||
|
|
||||||
|
line-height: 1.75rem; |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import './StepHeader.css'; |
||||||
|
|
||||||
|
export function StepHeader({ number, title }) { |
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
return <header className='SetupWizard__StepHeader'> |
||||||
|
<p className='SetupWizard__StepHeader-runningHead'>{t('Step')} {number}</p> |
||||||
|
<h2 className='SetupWizard__StepHeader-title'>{title}</h2> |
||||||
|
</header>; |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
.SetupWizard__Steps { |
||||||
|
flex: 1 1 auto; |
||||||
|
|
||||||
|
height: 100vh; |
||||||
|
padding: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__Steps-wrapper { |
||||||
|
overflow: auto; |
||||||
|
|
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
padding: 1rem 3rem; |
||||||
|
|
||||||
|
border-radius: 2px; |
||||||
|
background-color: var(--color-white); |
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
@media (width <= 760px) { |
||||||
|
.SetupWizard__Steps { |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import { AdminUserInformationStep } from './steps/AdminUserInformationStep'; |
||||||
|
import { SettingsBasedStep } from './steps/SettingsBasedStep'; |
||||||
|
import { RegisterServerStep } from './steps/RegisterServerStep'; |
||||||
|
import { useTranslation } from '../../hooks/useTranslation'; |
||||||
|
import { Epilogue } from './Epilogue'; |
||||||
|
import { SideBar } from './SideBar'; |
||||||
|
import { useSetupWizardStepsState, finalStep } from './StepsState'; |
||||||
|
|
||||||
|
export function Steps() { |
||||||
|
const { currentStep } = useSetupWizardStepsState(); |
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
if (currentStep === finalStep) { |
||||||
|
return <Epilogue />; |
||||||
|
} |
||||||
|
|
||||||
|
return <> |
||||||
|
<SideBar |
||||||
|
steps={[ |
||||||
|
{ |
||||||
|
step: 1, |
||||||
|
title: t('Admin_Info'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
step: 2, |
||||||
|
title: t('Organization_Info'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
step: 3, |
||||||
|
title: t('Server_Info'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
step: 4, |
||||||
|
title: t('Register_Server'), |
||||||
|
}, |
||||||
|
]} |
||||||
|
/> |
||||||
|
<section className='SetupWizard__Steps'> |
||||||
|
<div className='SetupWizard__Steps-wrapper'> |
||||||
|
<AdminUserInformationStep step={1} title={t('Admin_Info')} /> |
||||||
|
<SettingsBasedStep step={2} title={t('Organization_Info')} /> |
||||||
|
<SettingsBasedStep step={3} title={t('Server_Info')} /> |
||||||
|
<RegisterServerStep step={4} title={t('Register_Server')} /> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</>; |
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||||
|
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; |
||||||
|
|
||||||
|
import { useUserId } from '../../hooks/useUserId'; |
||||||
|
|
||||||
|
const Context = createContext(); |
||||||
|
|
||||||
|
export const useSetupWizardStepsState = () => useContext(Context); |
||||||
|
|
||||||
|
export const finalStep = 'final'; |
||||||
|
|
||||||
|
const useStepRouting = () => { |
||||||
|
const userId = useUserId(); |
||||||
|
const [currentStep, setCurrentStep] = useState(() => { |
||||||
|
const param = FlowRouter.getParam('step'); |
||||||
|
|
||||||
|
if (param === finalStep) { |
||||||
|
return finalStep; |
||||||
|
} |
||||||
|
|
||||||
|
const step = parseInt(param, 10); |
||||||
|
if (Number.isFinite(step) && step >= 1) { |
||||||
|
return step; |
||||||
|
} |
||||||
|
|
||||||
|
return 1; |
||||||
|
}); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!userId) { |
||||||
|
setCurrentStep(1); |
||||||
|
} else if (currentStep === 1) { |
||||||
|
setCurrentStep(2); |
||||||
|
} |
||||||
|
|
||||||
|
FlowRouter.withReplaceState(() => { |
||||||
|
FlowRouter.go('setup-wizard', { step: String(currentStep) }); |
||||||
|
}); |
||||||
|
}, [userId, currentStep]); |
||||||
|
|
||||||
|
return [currentStep, setCurrentStep]; |
||||||
|
}; |
||||||
|
|
||||||
|
export function StepsState({ children }) { |
||||||
|
const [currentStep, setCurrentStep] = useStepRouting(); |
||||||
|
|
||||||
|
const value = useMemo(() => ({ |
||||||
|
currentStep, |
||||||
|
goToPreviousStep: () => setCurrentStep(currentStep - 1), |
||||||
|
goToNextStep: () => setCurrentStep(currentStep + 1), |
||||||
|
goToFinalStep: () => setCurrentStep(finalStep), |
||||||
|
}), [currentStep]); |
||||||
|
|
||||||
|
return <Context.Provider value={value}> |
||||||
|
{children} |
||||||
|
</Context.Provider>; |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { settings } from '../../../app/settings/lib/settings'; |
||||||
|
|
||||||
|
const withPromisifiedReturn = (f) => (...args) => new Promise((resolve, reject) => { |
||||||
|
f(...args, (error, ...returnedValues) => { |
||||||
|
if (error) { |
||||||
|
reject(error); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
resolve(returnedValues); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
export const loginWithPassword = withPromisifiedReturn(Meteor.loginWithPassword.bind(Meteor)); |
||||||
|
|
||||||
|
export const batchSetSettings = withPromisifiedReturn(settings.batchSet.bind(settings)); |
||||||
|
|
||||||
|
export const setSetting = withPromisifiedReturn(settings.set.bind(settings)); |
||||||
@ -0,0 +1,156 @@ |
|||||||
|
import { Input, InputGroup } from '@rocket.chat/fuselage'; |
||||||
|
import { Session } from 'meteor/session'; |
||||||
|
import React, { useMemo, useState } from 'react'; |
||||||
|
import toastr from 'toastr'; |
||||||
|
|
||||||
|
import { call } from '../../../../app/ui-utils/client'; |
||||||
|
import { handleError } from '../../../../app/utils/client'; |
||||||
|
import { callbacks } from '../../../../app/callbacks/client'; |
||||||
|
import { useSetting } from '../../../hooks/useSetting'; |
||||||
|
import { useTranslation } from '../../../hooks/useTranslation'; |
||||||
|
import { useSetupWizardStepsState } from '../StepsState'; |
||||||
|
import { Step } from '../Step'; |
||||||
|
import { StepHeader } from '../StepHeader'; |
||||||
|
import { Pager } from '../Pager'; |
||||||
|
import { StepContent } from '../StepContent'; |
||||||
|
import { loginWithPassword } from '../functions'; |
||||||
|
import { useFocus } from '../../../hooks/useFocus'; |
||||||
|
|
||||||
|
const registerAdminUser = async ({ name, username, email, password, onRegistrationEmailSent }) => { |
||||||
|
await call('registerUser', { name, username, email, pass: password }); |
||||||
|
callbacks.run('userRegistered'); |
||||||
|
|
||||||
|
try { |
||||||
|
await loginWithPassword(email, password); |
||||||
|
} catch (error) { |
||||||
|
if (error.error === 'error-invalid-email') { |
||||||
|
onRegistrationEmailSent && onRegistrationEmailSent(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
throw error; |
||||||
|
} |
||||||
|
|
||||||
|
Session.set('forceLogin', false); |
||||||
|
|
||||||
|
await call('setUsername', username); |
||||||
|
|
||||||
|
callbacks.run('usernameSet'); |
||||||
|
}; |
||||||
|
|
||||||
|
export function AdminUserInformationStep({ step, title }) { |
||||||
|
const { currentStep, goToNextStep } = useSetupWizardStepsState(); |
||||||
|
const active = step === currentStep; |
||||||
|
|
||||||
|
const regexpForUsernameValidation = useSetting('UTF8_Names_Validation'); |
||||||
|
const usernameRegExp = useMemo(() => new RegExp(`^${ regexpForUsernameValidation }$`), [regexpForUsernameValidation]); |
||||||
|
const emailRegExp = useMemo(() => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i, []); |
||||||
|
|
||||||
|
const [name, setName] = useState(''); |
||||||
|
const [username, setUsername] = useState(''); |
||||||
|
const [email, setEmail] = useState(''); |
||||||
|
const [password, setPassword] = useState(''); |
||||||
|
|
||||||
|
const [isNameValid, validateName] = useState(true); |
||||||
|
const [isUsernameValid, validateUsername] = useState(true); |
||||||
|
const [isEmailValid, validateEmail] = useState(true); |
||||||
|
const [isPasswordValid, validatePassword] = useState(true); |
||||||
|
|
||||||
|
const isContinueEnabled = useMemo(() => name && username && email && password, [name, username, email, password]); |
||||||
|
|
||||||
|
const [commiting, setCommiting] = useState(false); |
||||||
|
|
||||||
|
const validate = () => { |
||||||
|
const isNameValid = !!name; |
||||||
|
const isUsernameValid = !!username && usernameRegExp.test(username); |
||||||
|
const isEmailValid = !!email && emailRegExp.test(email); |
||||||
|
const isPasswordValid = !!password; |
||||||
|
|
||||||
|
validateName(isNameValid); |
||||||
|
validateUsername(isUsernameValid); |
||||||
|
validateEmail(isEmailValid); |
||||||
|
validatePassword(isPasswordValid); |
||||||
|
|
||||||
|
return isNameValid && isUsernameValid && isEmailValid && isPasswordValid; |
||||||
|
}; |
||||||
|
|
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
const autoFocusRef = useFocus(active); |
||||||
|
|
||||||
|
const handleSubmit = async (event) => { |
||||||
|
event.preventDefault(); |
||||||
|
|
||||||
|
const canRegisterAdminUser = validate(); |
||||||
|
|
||||||
|
if (!canRegisterAdminUser) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
setCommiting(true); |
||||||
|
|
||||||
|
try { |
||||||
|
await registerAdminUser({ |
||||||
|
name, |
||||||
|
username, |
||||||
|
email, |
||||||
|
password, |
||||||
|
onRegistrationEmailSent: () => toastr.success(t('We_have_sent_registration_email')), |
||||||
|
}); |
||||||
|
goToNextStep(); |
||||||
|
} catch (error) { |
||||||
|
console.error(error); |
||||||
|
handleError(error); |
||||||
|
} finally { |
||||||
|
setCommiting(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return <Step active={active} working={commiting} onSubmit={handleSubmit}> |
||||||
|
<StepHeader number={step} title={title} /> |
||||||
|
|
||||||
|
<StepContent> |
||||||
|
<InputGroup> |
||||||
|
<Input |
||||||
|
ref={autoFocusRef} |
||||||
|
type='text' |
||||||
|
label={t('Name')} |
||||||
|
icon='user' |
||||||
|
placeholder={t('Type_your_name')} |
||||||
|
value={name} |
||||||
|
onChange={({ currentTarget: { value } }) => setName(value)} |
||||||
|
error={!isNameValid} |
||||||
|
/> |
||||||
|
<Input |
||||||
|
type='text' |
||||||
|
label={t('Username')} |
||||||
|
icon='at' |
||||||
|
placeholder={t('Type_your_username')} |
||||||
|
value={username} |
||||||
|
onChange={({ currentTarget: { value } }) => setUsername(value)} |
||||||
|
error={!isUsernameValid && t('Invalid_username')} |
||||||
|
/> |
||||||
|
<Input |
||||||
|
type='email' |
||||||
|
label={t('Organization_Email')} |
||||||
|
icon='mail' |
||||||
|
placeholder={t('Type_your_email')} |
||||||
|
value={email} |
||||||
|
onChange={({ currentTarget: { value } }) => setEmail(value)} |
||||||
|
error={!isEmailValid && t('Invalid_email')} |
||||||
|
/> |
||||||
|
<Input |
||||||
|
type='password' |
||||||
|
label={t('Password')} |
||||||
|
icon='key' |
||||||
|
placeholder={t('Type_your_password')} |
||||||
|
value={password} |
||||||
|
onChange={({ currentTarget: { value } }) => setPassword(value)} |
||||||
|
error={!isPasswordValid} |
||||||
|
/> |
||||||
|
</InputGroup> |
||||||
|
</StepContent> |
||||||
|
|
||||||
|
<Pager disabled={commiting} isContinueEnabled={isContinueEnabled} /> |
||||||
|
</Step>; |
||||||
|
} |
||||||
@ -0,0 +1,81 @@ |
|||||||
|
.SetupWizard__RegisterServerStep-text { |
||||||
|
margin-bottom: 3rem; |
||||||
|
|
||||||
|
color: var(--color-gray); |
||||||
|
|
||||||
|
font-size: 1rem; |
||||||
|
|
||||||
|
font-weight: 500; |
||||||
|
|
||||||
|
line-height: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__RegisterServerStep-content { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__RegisterServerStep-option { |
||||||
|
display: block; |
||||||
|
|
||||||
|
padding: 1.5rem; |
||||||
|
|
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
color: var(--color-dark); |
||||||
|
border: 2px solid var(--color-dark-10); |
||||||
|
|
||||||
|
border-radius: 2px; |
||||||
|
|
||||||
|
font-size: 0.875rem; |
||||||
|
|
||||||
|
line-height: 1.25rem; |
||||||
|
|
||||||
|
&:first-child { |
||||||
|
margin-bottom: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
&--selected { |
||||||
|
border-color: var(--rc-color-button-primary); |
||||||
|
} |
||||||
|
|
||||||
|
&--disabled { |
||||||
|
opacity: 0.25; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__RegisterServerStep-items { |
||||||
|
margin: 1rem 0; |
||||||
|
|
||||||
|
& + * { |
||||||
|
margin-top: 1rem; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.SetupWizard__RegisterServerStep-item { |
||||||
|
display: flex; |
||||||
|
|
||||||
|
margin: 0 -0.5rem 0.5rem; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&:last-child { |
||||||
|
margin-bottom: 0; |
||||||
|
} |
||||||
|
|
||||||
|
& > .SetupWizard__RegisterServerStep-item-icon { |
||||||
|
width: 20px; |
||||||
|
min-width: 20px; |
||||||
|
height: 20px; |
||||||
|
margin: 0 0.5rem; |
||||||
|
align-self: baseline; |
||||||
|
|
||||||
|
&--check { |
||||||
|
color: var(--rc-color-button-primary); |
||||||
|
} |
||||||
|
|
||||||
|
&--circle { |
||||||
|
height: 6px; |
||||||
|
margin: 7px 0.5rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,152 @@ |
|||||||
|
import { CheckBox, RadioButton } from '@rocket.chat/fuselage'; |
||||||
|
import React, { useState } from 'react'; |
||||||
|
|
||||||
|
import { call } from '../../../../app/ui-utils/client'; |
||||||
|
import { handleError } from '../../../../app/utils/client'; |
||||||
|
import { useTranslation } from '../../../hooks/useTranslation'; |
||||||
|
import { Icon } from '../../basic/Icon'; |
||||||
|
import { Pager } from '../Pager'; |
||||||
|
import { useSetupWizardParameters } from '../ParametersProvider'; |
||||||
|
import { Step } from '../Step'; |
||||||
|
import { StepContent } from '../StepContent'; |
||||||
|
import { StepHeader } from '../StepHeader'; |
||||||
|
import { useSetupWizardStepsState } from '../StepsState'; |
||||||
|
import { batchSetSettings } from '../functions'; |
||||||
|
import { useFocus } from '../../../hooks/useFocus'; |
||||||
|
|
||||||
|
const Option = React.forwardRef(({ children, label, selected, disabled, ...props }, ref) => |
||||||
|
<label |
||||||
|
className={[ |
||||||
|
'SetupWizard__RegisterServerStep-option', |
||||||
|
selected && 'SetupWizard__RegisterServerStep-option--selected', |
||||||
|
disabled && 'SetupWizard__RegisterServerStep-option--disabled', |
||||||
|
].filter(Boolean).join(' ')} |
||||||
|
> |
||||||
|
<RadioButton ref={ref} label={label} checked={selected} disabled={disabled} {...props} /> |
||||||
|
{children} |
||||||
|
</label> |
||||||
|
); |
||||||
|
|
||||||
|
const Items = (props) => <ul className='SetupWizard__RegisterServerStep-items' {...props} />; |
||||||
|
|
||||||
|
const Item = ({ children, icon, ...props }) => |
||||||
|
<li className='SetupWizard__RegisterServerStep-item' {...props}> |
||||||
|
<Icon block='SetupWizard__RegisterServerStep-item-icon' icon={icon} /> |
||||||
|
{children} |
||||||
|
</li>; |
||||||
|
|
||||||
|
export function RegisterServerStep({ step, title }) { |
||||||
|
const { canDeclineServerRegistration } = useSetupWizardParameters(); |
||||||
|
const { currentStep, goToPreviousStep, goToFinalStep } = useSetupWizardStepsState(); |
||||||
|
|
||||||
|
const active = step === currentStep; |
||||||
|
|
||||||
|
const [registerServer, setRegisterServer] = useState(true); |
||||||
|
const [optInMarketingEmails, setOptInMarketingEmails] = useState(true); |
||||||
|
|
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
const [commiting, setComitting] = useState(false); |
||||||
|
|
||||||
|
const handleBackClick = () => { |
||||||
|
goToPreviousStep(); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (event) => { |
||||||
|
event.preventDefault(); |
||||||
|
|
||||||
|
setComitting(true); |
||||||
|
|
||||||
|
try { |
||||||
|
await batchSetSettings([ |
||||||
|
{ |
||||||
|
_id: 'Statistics_reporting', |
||||||
|
value: registerServer, |
||||||
|
}, |
||||||
|
{ |
||||||
|
_id: 'Apps_Framework_enabled', |
||||||
|
value: registerServer, |
||||||
|
}, |
||||||
|
{ |
||||||
|
_id: 'Register_Server', |
||||||
|
value: registerServer, |
||||||
|
}, |
||||||
|
{ |
||||||
|
_id: 'Allow_Marketing_Emails', |
||||||
|
value: optInMarketingEmails, |
||||||
|
}, |
||||||
|
]); |
||||||
|
|
||||||
|
if (registerServer) { |
||||||
|
await call('cloud:registerWorkspace'); |
||||||
|
} |
||||||
|
|
||||||
|
setComitting(false); |
||||||
|
goToFinalStep(); |
||||||
|
} catch (error) { |
||||||
|
console.error(error); |
||||||
|
handleError(error); |
||||||
|
setComitting(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const autoFocusRef = useFocus(active); |
||||||
|
|
||||||
|
return <Step active={active} working={commiting} onSubmit={handleSubmit}> |
||||||
|
<StepHeader number={step} title={title} /> |
||||||
|
|
||||||
|
<StepContent> |
||||||
|
<p className='SetupWizard__RegisterServerStep-text'>{t('Register_Server_Info')}</p> |
||||||
|
|
||||||
|
<div className='SetupWizard__RegisterServerStep-content'> |
||||||
|
<Option |
||||||
|
ref={autoFocusRef} |
||||||
|
label={t('Register_Server_Registered')} |
||||||
|
name='registerServer' |
||||||
|
value='true' |
||||||
|
selected={registerServer} |
||||||
|
onChange={({ currentTarget: { checked } }) => { |
||||||
|
setRegisterServer(checked); |
||||||
|
setOptInMarketingEmails(checked); |
||||||
|
}} |
||||||
|
> |
||||||
|
<Items> |
||||||
|
<Item icon='check'>{t('Register_Server_Registered_Push_Notifications')}</Item> |
||||||
|
<Item icon='check'>{t('Register_Server_Registered_Livechat')}</Item> |
||||||
|
<Item icon='check'>{t('Register_Server_Registered_OAuth')}</Item> |
||||||
|
<Item icon='check'>{t('Register_Server_Registered_Marketplace')}</Item> |
||||||
|
</Items> |
||||||
|
<CheckBox |
||||||
|
name='optInMarketingEmails' |
||||||
|
value='true' |
||||||
|
label={t('Register_Server_Opt_In')} |
||||||
|
disabled={!registerServer} |
||||||
|
checked={optInMarketingEmails} |
||||||
|
onChange={({ currentTarget: { checked } }) => { |
||||||
|
setOptInMarketingEmails(checked); |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Option> |
||||||
|
<Option |
||||||
|
label={t('Register_Server_Standalone')} |
||||||
|
name='registerServer' |
||||||
|
value='false' |
||||||
|
disabled={!canDeclineServerRegistration} |
||||||
|
selected={!registerServer} |
||||||
|
onChange={({ currentTarget: { checked } }) => { |
||||||
|
setRegisterServer(!checked); |
||||||
|
setOptInMarketingEmails(!checked); |
||||||
|
}} |
||||||
|
> |
||||||
|
<Items> |
||||||
|
<Item icon='circle'>{t('Register_Server_Standalone_Service_Providers')}</Item> |
||||||
|
<Item icon='circle'>{t('Register_Server_Standalone_Update_Settings')}</Item> |
||||||
|
<Item icon='circle'>{t('Register_Server_Standalone_Own_Certificates')}</Item> |
||||||
|
</Items> |
||||||
|
</Option> |
||||||
|
</div> |
||||||
|
</StepContent> |
||||||
|
|
||||||
|
<Pager disabled={commiting} onBackClick={handleBackClick} /> |
||||||
|
</Step>; |
||||||
|
} |
||||||
@ -0,0 +1,141 @@ |
|||||||
|
import { Input, InputGroup } from '@rocket.chat/fuselage'; |
||||||
|
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; |
||||||
|
import React, { Fragment, useEffect, useReducer, useState } from 'react'; |
||||||
|
|
||||||
|
import { handleError } from '../../../../app/utils/client'; |
||||||
|
import { useTranslation } from '../../../hooks/useTranslation'; |
||||||
|
import { useReactiveValue } from '../../../hooks/useReactiveValue'; |
||||||
|
import { Pager } from '../Pager'; |
||||||
|
import { useSetupWizardParameters } from '../ParametersProvider'; |
||||||
|
import { useSetupWizardStepsState } from '../StepsState'; |
||||||
|
import { Step } from '../Step'; |
||||||
|
import { StepHeader } from '../StepHeader'; |
||||||
|
import { StepContent } from '../StepContent'; |
||||||
|
import { batchSetSettings } from '../functions'; |
||||||
|
import { useFocus } from '../../../hooks/useFocus'; |
||||||
|
|
||||||
|
const useFields = () => { |
||||||
|
const reset = 'RESET'; |
||||||
|
const setValue = 'SET_VALUE'; |
||||||
|
|
||||||
|
const [fields, dispatch] = useReducer((fields, { type, payload }) => { |
||||||
|
if (type === reset) { |
||||||
|
return payload; |
||||||
|
} |
||||||
|
|
||||||
|
if (type === setValue) { |
||||||
|
const { _id, value } = payload; |
||||||
|
return fields.map((field) => (field._id === _id ? { ...field, value } : field)); |
||||||
|
} |
||||||
|
|
||||||
|
return fields; |
||||||
|
}, []); |
||||||
|
|
||||||
|
const resetFields = (fields) => dispatch({ type: reset, payload: fields }); |
||||||
|
const setFieldValue = (_id, value) => dispatch({ type: setValue, payload: { _id, value } }); |
||||||
|
|
||||||
|
return { fields, resetFields, setFieldValue }; |
||||||
|
}; |
||||||
|
|
||||||
|
export function SettingsBasedStep({ step, title }) { |
||||||
|
const { settings } = useSetupWizardParameters(); |
||||||
|
const { currentStep, goToPreviousStep, goToNextStep } = useSetupWizardStepsState(); |
||||||
|
const { fields, resetFields, setFieldValue } = useFields(); |
||||||
|
const [commiting, setCommiting] = useState(false); |
||||||
|
|
||||||
|
const active = step === currentStep; |
||||||
|
|
||||||
|
const languages = useReactiveValue(() => TAPi18n.getLanguages(), []); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
resetFields( |
||||||
|
settings |
||||||
|
.filter(({ wizard }) => wizard.step === step) |
||||||
|
.filter(({ type }) => ['string', 'select', 'language'].includes(type)) |
||||||
|
.sort(({ wizard: { order: a } }, { wizard: { order: b } }) => a - b) |
||||||
|
.map(({ value, ...field }) => ({ ...field, value: value || '' })) |
||||||
|
); |
||||||
|
}, [settings, currentStep]); |
||||||
|
|
||||||
|
const t = useTranslation(); |
||||||
|
|
||||||
|
const handleBackClick = () => { |
||||||
|
goToPreviousStep(); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = async (event) => { |
||||||
|
event.preventDefault(); |
||||||
|
|
||||||
|
setCommiting(true); |
||||||
|
|
||||||
|
try { |
||||||
|
await batchSetSettings(fields.map(({ _id, value }) => ({ _id, value }))); |
||||||
|
goToNextStep(); |
||||||
|
} catch (error) { |
||||||
|
console.error(error); |
||||||
|
handleError(error); |
||||||
|
} finally { |
||||||
|
setCommiting(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const autoFocusRef = useFocus(active); |
||||||
|
|
||||||
|
return <Step active={active} working={commiting} onSubmit={handleSubmit}> |
||||||
|
<StepHeader number={step} title={title} /> |
||||||
|
|
||||||
|
<StepContent> |
||||||
|
<InputGroup> |
||||||
|
{fields.map(({ _id, type, i18nLabel, value, values }, i) => |
||||||
|
<Fragment key={i}> |
||||||
|
{type === 'string' |
||||||
|
&& <Input |
||||||
|
type='text' |
||||||
|
label={t(i18nLabel)} |
||||||
|
name={_id} |
||||||
|
ref={i === 0 ? autoFocusRef : undefined} |
||||||
|
value={value} |
||||||
|
onChange={({ currentTarget: { value } }) => setFieldValue(_id, value)} |
||||||
|
/>} |
||||||
|
|
||||||
|
{type === 'select' |
||||||
|
&& <Input |
||||||
|
type='select' |
||||||
|
label={t(i18nLabel)} |
||||||
|
name={_id} |
||||||
|
placeholder={t('Select_an_option')} |
||||||
|
ref={i === 0 ? autoFocusRef : undefined} |
||||||
|
value={value} |
||||||
|
onChange={({ currentTarget: { value } }) => setFieldValue(_id, value)} |
||||||
|
> |
||||||
|
{values |
||||||
|
.map(({ i18nLabel, key }) => ({ label: t(i18nLabel), value: key })) |
||||||
|
.map(({ label, value }) => <option key={value} value={value}>{label}</option>)} |
||||||
|
</Input>} |
||||||
|
|
||||||
|
{type === 'language' |
||||||
|
&& <Input |
||||||
|
type='select' |
||||||
|
label={t(i18nLabel)} |
||||||
|
name={_id} |
||||||
|
placeholder={t('Default')} |
||||||
|
ref={i === 0 ? autoFocusRef : undefined} |
||||||
|
value={value} |
||||||
|
onChange={({ currentTarget: { value } }) => setFieldValue(_id, value)} |
||||||
|
> |
||||||
|
{Object.entries(languages) |
||||||
|
.map(([key, { name }]) => ({ label: name, value: key })) |
||||||
|
.sort((a, b) => a.key - b.key) |
||||||
|
.map(({ label, value }) => <option key={value} value={value}>{label}</option>)} |
||||||
|
</Input>} |
||||||
|
</Fragment> |
||||||
|
)} |
||||||
|
</InputGroup> |
||||||
|
</StepContent> |
||||||
|
|
||||||
|
<Pager |
||||||
|
disabled={commiting} |
||||||
|
onBackClick={currentStep > 2 && handleBackClick} |
||||||
|
/> |
||||||
|
</Step>; |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
import { useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
export const useFocus = (isFocused) => { |
||||||
|
const [element, setElement] = useState(null); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (isFocused && element) { |
||||||
|
element.focus(); |
||||||
|
} |
||||||
|
}, [element, isFocused]); |
||||||
|
|
||||||
|
return (ref) => { |
||||||
|
setElement(ref); |
||||||
|
}; |
||||||
|
}; |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
import { Tracker } from 'meteor/tracker'; |
||||||
|
import { useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
export const useReactiveValue = (getValue, deps = []) => { |
||||||
|
const [value, setValue] = useState(getValue); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const computation = Tracker.autorun(() => { |
||||||
|
const newValue = getValue(); |
||||||
|
setValue(() => newValue); |
||||||
|
}); |
||||||
|
|
||||||
|
return () => { |
||||||
|
computation.stop(); |
||||||
|
}; |
||||||
|
}, deps); |
||||||
|
|
||||||
|
return value; |
||||||
|
}; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
import { Session } from 'meteor/session'; |
||||||
|
|
||||||
|
import { useReactiveValue } from './useReactiveValue'; |
||||||
|
|
||||||
|
export const useSession = (variableName) => useReactiveValue(() => Session.get(variableName)); |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
import { settings } from '../../app/settings/client'; |
||||||
|
import { useReactiveValue } from './useReactiveValue'; |
||||||
|
|
||||||
|
export const useSetting = (settingName) => useReactiveValue(() => settings.get(settingName)); |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; |
||||||
|
import { Tracker } from 'meteor/tracker'; |
||||||
|
|
||||||
|
import { useReactiveValue } from './useReactiveValue'; |
||||||
|
|
||||||
|
const translator = (key, ...replaces) => Tracker.nonreactive(() => { |
||||||
|
if (typeof replaces[0] === 'object') { |
||||||
|
return TAPi18n.__(key, ...replaces); |
||||||
|
} |
||||||
|
|
||||||
|
return TAPi18n.__(key, { |
||||||
|
postProcess: 'sprintf', |
||||||
|
sprintf: replaces, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
export const useTranslation = () => { |
||||||
|
useReactiveValue(() => TAPi18n.getLanguage()); |
||||||
|
|
||||||
|
return translator; |
||||||
|
}; |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { useReactiveValue } from './useReactiveValue'; |
||||||
|
|
||||||
|
export const useUserId = () => useReactiveValue(() => Meteor.userId()); |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
import { useLayoutEffect } from 'react'; |
||||||
|
|
||||||
|
export const useWipeInitialPageLoading = () => { |
||||||
|
useLayoutEffect(() => { |
||||||
|
const initialPageLoadingElement = document.getElementById('initial-page-loading'); |
||||||
|
|
||||||
|
if (!initialPageLoadingElement) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
initialPageLoadingElement.style.display = 'none'; |
||||||
|
|
||||||
|
return () => { |
||||||
|
initialPageLoadingElement.style.display = 'flex'; |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
}; |
||||||
@ -1,9 +0,0 @@ |
|||||||
<template name="pageNotFound"> |
|
||||||
<section class = "page-not-found-container content-background-color"> |
|
||||||
<span class = "error-404">404</span> |
|
||||||
<span class = "page-not-found-message">{{_ "Oops_page_not_found"}}</span> |
|
||||||
<span class = "page-not-found-description">{{_ "Sorry_page_you_requested_does_not_exists_or_was_deleted"}}</span> |
|
||||||
<button class = "page-not-found-button page-not-found-button-previous rc-button rc-button--primary">{{_ "Return_to_previous_page"}}</button> |
|
||||||
<button class = "page-not-found-button page-not-found-button-home rc-button rc-button--primary">{{_ "Return_to_home"}}</button> |
|
||||||
</section> |
|
||||||
</template> |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
import { Template } from 'meteor/templating'; |
|
||||||
import { FlowRouter } from 'meteor/kadira:flow-router'; |
|
||||||
|
|
||||||
import './pageNotFound.html'; |
|
||||||
|
|
||||||
Template.pageNotFound.onRendered(function() { |
|
||||||
const parent = document.querySelector('.page-loading'); |
|
||||||
const child = document.querySelector('.loading-animation'); |
|
||||||
parent.removeChild(child); |
|
||||||
}); |
|
||||||
|
|
||||||
Template.pageNotFound.events({ |
|
||||||
'click .page-not-found-button-home'(e) { |
|
||||||
e.preventDefault(); |
|
||||||
FlowRouter.go('home'); |
|
||||||
}, |
|
||||||
|
|
||||||
'click .page-not-found-button-previous'(e) { |
|
||||||
e.preventDefault(); |
|
||||||
window.history.back(); |
|
||||||
}, |
|
||||||
}); |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
.page-not-found-container { |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
padding: 10%; |
|
||||||
|
|
||||||
text-align: center; |
|
||||||
|
|
||||||
color: white; |
|
||||||
background-image: url("/images/404.svg"); |
|
||||||
background-repeat: no-repeat; |
|
||||||
background-position: center; |
|
||||||
background-size: cover; |
|
||||||
|
|
||||||
.error-404 { |
|
||||||
display: block; |
|
||||||
|
|
||||||
padding: 10px; |
|
||||||
|
|
||||||
font-size: 4em; |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
|
|
||||||
.page-not-found-message { |
|
||||||
display: block; |
|
||||||
|
|
||||||
padding: 10px; |
|
||||||
|
|
||||||
font-size: 2em; |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
|
|
||||||
.page-not-found-description { |
|
||||||
display: block; |
|
||||||
|
|
||||||
padding: 10px; |
|
||||||
|
|
||||||
font-size: 1em; |
|
||||||
} |
|
||||||
|
|
||||||
.page-not-found-button { |
|
||||||
display: inline-block; |
|
||||||
|
|
||||||
margin: 20px 10px 0; |
|
||||||
padding: 8px 16px; |
|
||||||
|
|
||||||
font-size: 1em; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1 @@ |
|||||||
|
../../node_modules/@rocket.chat/icons/dist/font/RocketChat.eot |
||||||
@ -0,0 +1 @@ |
|||||||
|
../../node_modules/@rocket.chat/icons/dist/font/RocketChat.svg |
||||||
@ -0,0 +1 @@ |
|||||||
|
../../node_modules/@rocket.chat/icons/dist/font/RocketChat.ttf |
||||||
@ -0,0 +1 @@ |
|||||||
|
../../node_modules/@rocket.chat/icons/dist/font/RocketChat.woff |
||||||
@ -0,0 +1 @@ |
|||||||
|
../../node_modules/@rocket.chat/icons/dist/font/RocketChat.woff2 |
||||||
@ -1,17 +1,9 @@ |
|||||||
import { Meteor } from 'meteor/meteor'; |
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
import { hasRole } from '../../authorization'; |
import { Settings } from '../../app/models'; |
||||||
import { Settings } from '../../models'; |
|
||||||
|
|
||||||
Meteor.methods({ |
Meteor.methods({ |
||||||
getSetupWizardParameters() { |
getSetupWizardParameters() { |
||||||
const userId = Meteor.userId(); |
|
||||||
const userHasAdminRole = userId && hasRole(userId, 'admin'); |
|
||||||
|
|
||||||
if (!userHasAdminRole) { |
|
||||||
throw new Meteor.Error('error-not-allowed'); |
|
||||||
} |
|
||||||
|
|
||||||
const settings = Settings.findSetupWizardSettings().fetch(); |
const settings = Settings.findSetupWizardSettings().fetch(); |
||||||
const allowStandaloneServer = process.env.DEPLOY_PLATFORM !== 'rocket-cloud'; |
const allowStandaloneServer = process.env.DEPLOY_PLATFORM !== 'rocket-cloud'; |
||||||
|
|
||||||
Loading…
Reference in new issue