[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 { hasRole } from '../../authorization'; |
||||
import { Settings } from '../../models'; |
||||
import { Settings } from '../../app/models'; |
||||
|
||||
Meteor.methods({ |
||||
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 allowStandaloneServer = process.env.DEPLOY_PLATFORM !== 'rocket-cloud'; |
||||
|
||||
Loading…
Reference in new issue