feat: implement onboarding page
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.60846 1.61513C2.1087 1.3432 1.5 1.70497 1.5 2.27392V15.7262C1.5 16.2952 2.1087 16.6569 2.60846 16.385L14.97 9.65887C15.4921 9.37481 15.4921 8.62534 14.97 8.34128L2.60846 1.61513ZM0 2.27392C0 0.567069 1.82609 -0.518242 3.32538 0.297548L15.687 7.0237C17.2531 7.87588 17.2531 10.1243 15.687 10.9764L3.32538 17.7026C1.82609 18.5184 0 17.4331 0 15.7262V2.27392Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
@@ -0,0 +1,91 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import { v4 as UUIDv4 } from "uuid";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
|
const StyledForm = styled.form`
|
||||||
|
> .option {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
& + .box {
|
||||||
|
width: 512px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all ease-in-out 250ms;
|
||||||
|
|
||||||
|
& > label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #667085;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all ease-in-out 250ms;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: inset 0 0 0 4px #ffffff;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
margin: 14px 8px 14px 14px;
|
||||||
|
transition: all ease-in-out 500ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .box {
|
||||||
|
background: #22ccee;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
|
||||||
|
& > label {
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: inset 0 0 0 4px #22ccee;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function Radio({ options, value = undefined, onChange = undefined }) {
|
||||||
|
const [innerValue, setInnerValue] = useState(0);
|
||||||
|
const id = useRef(UUIDv4());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm>
|
||||||
|
{options.map((item, index) => (
|
||||||
|
<div className="option" key={index}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={(value !== undefined ? value : innerValue) === index}
|
||||||
|
onChange={() => {
|
||||||
|
value === undefined && setInnerValue(index);
|
||||||
|
onChange !== null && onChange(index);
|
||||||
|
}}
|
||||||
|
id={`${id.current}-${index}`}
|
||||||
|
/>
|
||||||
|
<div className="box">
|
||||||
|
<label htmlFor={`${id.current}-${index}`}>{item}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
+102
-103
@@ -21,115 +21,114 @@ import store from "../app/store";
|
|||||||
import InvitePage from "./invite";
|
import InvitePage from "./invite";
|
||||||
import SettingPage from "./setting";
|
import SettingPage from "./setting";
|
||||||
import SettingChannelPage from "./settingChannel";
|
import SettingChannelPage from "./settingChannel";
|
||||||
|
import OnboardingPage from "./onboarding";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import ResourceManagement from "./resources";
|
import ResourceManagement from "./resources";
|
||||||
|
|
||||||
const PageRoutes = () => {
|
const PageRoutes = () => {
|
||||||
const {
|
const {
|
||||||
ui: { online },
|
ui: { online },
|
||||||
fileMessages,
|
fileMessages
|
||||||
} = useSelector((store) => {
|
} = useSelector((store) => {
|
||||||
return { ui: store.ui, fileMessages: store.fileMessage };
|
return { ui: store.ui, fileMessages: store.fileMessage };
|
||||||
});
|
});
|
||||||
// 掉线检测
|
// 掉线检测
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let toastId = 0;
|
let toastId = 0;
|
||||||
if (!online) {
|
if (!online) {
|
||||||
toast.error("Network Offline!", { duration: Infinity });
|
toast.error("Network Offline!", { duration: Infinity });
|
||||||
} else {
|
} else {
|
||||||
toast.dismiss(toastId);
|
toast.dismiss(toastId);
|
||||||
}
|
}
|
||||||
}, [online]);
|
}, [online]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/oauth/:token" element={<OAuthPage />} />
|
<Route path="/oauth/:token" element={<OAuthPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
<RequireNoAuth>
|
<RequireNoAuth>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</RequireNoAuth>
|
</RequireNoAuth>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/send_magic_link"
|
path="/send_magic_link"
|
||||||
element={
|
element={
|
||||||
<RequireNoAuth>
|
<RequireNoAuth>
|
||||||
<SendMagicLinkPage />
|
<SendMagicLinkPage />
|
||||||
</RequireNoAuth>
|
</RequireNoAuth>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/reg"
|
path="/reg"
|
||||||
element={
|
element={
|
||||||
<RequireNoAuth>
|
<RequireNoAuth>
|
||||||
<RegBasePage />
|
<RegBasePage />
|
||||||
</RequireNoAuth>
|
</RequireNoAuth>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route index element={<RegPage />} />
|
<Route index element={<RegPage />} />
|
||||||
<Route path="magiclink">
|
<Route path="magiclink">
|
||||||
<Route index element={<RegWithUsernamePage />} />
|
<Route index element={<RegWithUsernamePage />} />
|
||||||
<Route path=":token" element={<RegWithUsernamePage />} />
|
<Route path=":token" element={<RegWithUsernamePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path="/email_login"
|
path="/email_login"
|
||||||
element={
|
element={
|
||||||
<RequireNoAuth>
|
<RequireNoAuth>
|
||||||
<SendMagicLinkPage />
|
<SendMagicLinkPage />
|
||||||
</RequireNoAuth>
|
</RequireNoAuth>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/invite" element={<InvitePage />} />
|
<Route path="/invite" element={<InvitePage />} />
|
||||||
<Route
|
<Route path="/onboarding" element={<OnboardingPage />} />
|
||||||
path="/"
|
<Route
|
||||||
element={
|
path="/"
|
||||||
<RequireAuth>
|
element={
|
||||||
<HomePage />
|
<RequireAuth>
|
||||||
</RequireAuth>
|
<HomePage />
|
||||||
}
|
</RequireAuth>
|
||||||
>
|
}
|
||||||
<Route path="setting">
|
>
|
||||||
<Route index element={<SettingPage />} />
|
<Route path="setting">
|
||||||
<Route path="channel/:cid" element={<SettingChannelPage />} />
|
<Route index element={<SettingPage />} />
|
||||||
</Route>
|
<Route path="channel/:cid" element={<SettingChannelPage />} />
|
||||||
<Route index element={<ChatPage />} />
|
</Route>
|
||||||
<Route path="chat">
|
<Route index element={<ChatPage />} />
|
||||||
<Route index element={<ChatPage />} />
|
<Route path="chat">
|
||||||
<Route path="channel/:channel_id" element={<ChatPage />} />
|
<Route index element={<ChatPage />} />
|
||||||
<Route path="dm/:user_id" element={<ChatPage />} />
|
<Route path="channel/:channel_id" element={<ChatPage />} />
|
||||||
</Route>
|
<Route path="dm/:user_id" element={<ChatPage />} />
|
||||||
<Route path="contacts">
|
</Route>
|
||||||
<Route index element={<ContactsPage />} />
|
<Route path="contacts">
|
||||||
<Route path=":user_id" element={<ContactsPage />} />
|
<Route index element={<ContactsPage />} />
|
||||||
</Route>
|
<Route path=":user_id" element={<ContactsPage />} />
|
||||||
<Route path="favs" element={<FavoritesPage />}></Route>
|
</Route>
|
||||||
<Route
|
<Route path="favs" element={<FavoritesPage />}></Route>
|
||||||
path="files"
|
<Route path="files" element={<ResourceManagement fileMessages={fileMessages} />}></Route>
|
||||||
element={<ResourceManagement fileMessages={fileMessages} />}
|
</Route>
|
||||||
></Route>
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Route>
|
</Routes>
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
</HashRouter>
|
||||||
</Routes>
|
);
|
||||||
</HashRouter>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
// const local_key = "AUTH_DATA";
|
// const local_key = "AUTH_DATA";
|
||||||
export default function ReduxRoutes() {
|
export default function ReduxRoutes() {
|
||||||
// const [authData, setAuthData] = useState(
|
// const [authData, setAuthData] = useState(
|
||||||
// JSON.parse(localStorage.getItem(local_key))
|
// JSON.parse(localStorage.getItem(local_key))
|
||||||
// );
|
// );
|
||||||
// const updateAuthData = (data) => {
|
// const updateAuthData = (data) => {
|
||||||
// localStorage.setItem(local_key, JSON.stringify(data));
|
// localStorage.setItem(local_key, JSON.stringify(data));
|
||||||
// setAuthData(data);
|
// setAuthData(data);
|
||||||
// };
|
// };
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Meta />
|
<Meta />
|
||||||
<PageRoutes />
|
<PageRoutes />
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import StyledButton from "../../common/component/styled/Button";
|
||||||
|
import FirstStep from "./steps/first";
|
||||||
|
import SpaceNameStep from "./steps/spaceName";
|
||||||
|
import AdminCredentialsStep from "./steps/adminCredentials";
|
||||||
|
import InviteRuleStep from "./steps/inviteRule";
|
||||||
|
import InviteLinkStep from "./steps/inviteLink";
|
||||||
|
import LastStep from "./steps/last";
|
||||||
|
import StyledOnboardingPage from "./styled";
|
||||||
|
|
||||||
|
export default function OnboardingPage() {
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const [data, setData] = useState({
|
||||||
|
spaceName: "",
|
||||||
|
adminEmail: "",
|
||||||
|
adminPassword: "",
|
||||||
|
adminPassword2: "",
|
||||||
|
inviteRule: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledOnboardingPage>
|
||||||
|
<div className="horizontalBox">
|
||||||
|
<div className="verticalBox">
|
||||||
|
{step > 0 && step < 5 && (
|
||||||
|
<>
|
||||||
|
<StyledButton className="buttonBack ghost border_less" onClick={() => setStep(step - 1)}>
|
||||||
|
Back
|
||||||
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
className="buttonNext"
|
||||||
|
onClick={() => {
|
||||||
|
if (step === 1) {
|
||||||
|
// Verification for space name
|
||||||
|
if (data.spaceName === "") {
|
||||||
|
toast.error("Please enter space name!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStep(step + 1);
|
||||||
|
} else if (step === 2) {
|
||||||
|
// Verification for admin credentials
|
||||||
|
if (data.adminEmail === "") {
|
||||||
|
toast.error("Please enter admin email!");
|
||||||
|
return;
|
||||||
|
} else if (data.adminPassword === "") {
|
||||||
|
toast.error("Please enter admin password!");
|
||||||
|
return;
|
||||||
|
} else if (data.adminPassword !== data.adminPassword2) {
|
||||||
|
toast.error("Two passwords do not match!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStep(step + 1);
|
||||||
|
} else if (step === 3) {
|
||||||
|
// Verification for invitation rule
|
||||||
|
if (data.inviteRule === null) {
|
||||||
|
toast.error("Please choose one option!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStep(step + 1);
|
||||||
|
} else if (step === 4) {
|
||||||
|
// Verification for invitation link
|
||||||
|
setStep(step + 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{step === 0 && <FirstStep onButtonClick={() => setStep(step + 1)} />}
|
||||||
|
{step === 1 && <SpaceNameStep data={data} setData={setData} />}
|
||||||
|
{step === 2 && <AdminCredentialsStep data={data} setData={setData} />}
|
||||||
|
{step === 3 && <InviteRuleStep data={data} setData={setData} />}
|
||||||
|
{step === 4 && <InviteLinkStep />}
|
||||||
|
{step === 5 && (
|
||||||
|
<LastStep
|
||||||
|
data={data}
|
||||||
|
onButtonClick={() => {
|
||||||
|
// TODO: finish it
|
||||||
|
console.log("onboarding steps completed:", data);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledOnboardingPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledInput from "../../../common/component/styled/Input";
|
||||||
|
|
||||||
|
const StyledAdminCredentialsStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .input:not(:last-child) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function AdminCredentialsStep({ data, setData }) {
|
||||||
|
return (
|
||||||
|
<StyledAdminCredentialsStep>
|
||||||
|
<span className="primaryText">Now let’s set up your admin account</span>
|
||||||
|
<span className="secondaryText">You are the 1st user and admin of your space!</span>
|
||||||
|
<StyledInput
|
||||||
|
className="input"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
value={data.adminEmail}
|
||||||
|
onChange={(e) =>
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
adminEmail: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledInput
|
||||||
|
className="input"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={data.adminPassword}
|
||||||
|
onChange={(e) =>
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
adminPassword: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledInput
|
||||||
|
className="input"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
value={data.adminPassword2}
|
||||||
|
onChange={(e) =>
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
adminPassword2: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledAdminCredentialsStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledButton from "../../../common/component/styled/Button";
|
||||||
|
import PlayIcon from "../../../assets/icons/play.svg?url";
|
||||||
|
|
||||||
|
const StyledFirstStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function FirstStep({ onButtonClick }) {
|
||||||
|
return (
|
||||||
|
<StyledFirstStep>
|
||||||
|
<span className="primaryText">Welcome to your Rustchat!</span>
|
||||||
|
<span className="secondaryText">
|
||||||
|
Everything in this space is owned by you. Let’s set up your space!
|
||||||
|
</span>
|
||||||
|
<StyledButton className="startButton" onClick={onButtonClick}>
|
||||||
|
<img src={PlayIcon} alt="play icon" />
|
||||||
|
<span>Start</span>
|
||||||
|
</StyledButton>
|
||||||
|
</StyledFirstStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledInput from "../../../common/component/styled/Input";
|
||||||
|
import StyledButton from "../../../common/component/styled/Button";
|
||||||
|
import useInviteLink from "../../../common/hook/useInviteLink";
|
||||||
|
|
||||||
|
const StyledInviteLinkStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .secondaryText {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tip {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #475467;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .link {
|
||||||
|
position: relative;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #f4f4f5;
|
||||||
|
box-shadow: 0 1px 2px rgba(31, 41, 55, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 374px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 11px 0 11px 8px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #78787c;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: 0 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #22ccee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function InviteLinkStep() {
|
||||||
|
const { link, linkCopied, copyLink } = useInviteLink();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledInviteLinkStep>
|
||||||
|
<span className="primaryText">Last step: invite others!</span>
|
||||||
|
<span className="secondaryText">Now let’s invite others!</span>
|
||||||
|
<span className="tip">Send invitation link to your future community members:</span>
|
||||||
|
<div className="link">
|
||||||
|
<StyledInput className="large" readOnly placeholder="Generating" value={link} />
|
||||||
|
<StyledButton onClick={copyLink} className="ghost small border_less">
|
||||||
|
{linkCopied ? "Copied" : `Copy`}
|
||||||
|
</StyledButton>
|
||||||
|
</div>
|
||||||
|
</StyledInviteLinkStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledRadio from "../../../common/component/styled/Radio";
|
||||||
|
|
||||||
|
const StyledInviteRuleStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .input:not(:last-child) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function InviteRuleStep({ data, setData }) {
|
||||||
|
return (
|
||||||
|
<StyledInviteRuleStep>
|
||||||
|
<span className="primaryText">Last step: invite others!</span>
|
||||||
|
<span className="secondaryText">Firstly, who can sign up to this server?</span>
|
||||||
|
<StyledRadio
|
||||||
|
options={["Everyone", "Invitation link only"]}
|
||||||
|
value={data.inviteRule}
|
||||||
|
onChange={(v) =>
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
inviteRule: v
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledInviteRuleStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledButton from "../../../common/component/styled/Button";
|
||||||
|
import PlayIcon from "../../../assets/icons/play.svg?url";
|
||||||
|
|
||||||
|
const StyledLastStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .secondaryText {
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tip {
|
||||||
|
width: 588px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 96px;
|
||||||
|
|
||||||
|
> .strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function LastStep({ data, onButtonClick }) {
|
||||||
|
return (
|
||||||
|
<StyledLastStep>
|
||||||
|
<span className="primaryText">Welcome to {data.spaceName}</span>
|
||||||
|
<span className="secondaryText">Proudly presented by Rustchat</span>
|
||||||
|
<span className="tip">
|
||||||
|
More settings, including domain resolution, privileges, securities, and invites are available in{" "}
|
||||||
|
<span className="strong">Settings</span>
|
||||||
|
</span>
|
||||||
|
<StyledButton className="startButton" onClick={onButtonClick}>
|
||||||
|
<img src={PlayIcon} alt="play icon" />
|
||||||
|
<span>Start</span>
|
||||||
|
</StyledButton>
|
||||||
|
</StyledLastStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
import StyledInput from "../../../common/component/styled/Input";
|
||||||
|
|
||||||
|
const StyledSpaceNameStep = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .secondaryText {
|
||||||
|
color: #667085;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function SpaceNameStep({ data, setData }) {
|
||||||
|
return (
|
||||||
|
<StyledSpaceNameStep>
|
||||||
|
<span className="primaryText">Let’s start with name:</span>
|
||||||
|
<span className="secondaryText">This space is called: </span>
|
||||||
|
<StyledInput
|
||||||
|
className="input"
|
||||||
|
placeholder="Enter space name"
|
||||||
|
value={data.spaceName}
|
||||||
|
onChange={(e) =>
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
spaceName: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledSpaceNameStep>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const StyledOnboardingPage = styled.div`
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
> .horizontalBox {
|
||||||
|
width: calc(100vw - 40px);
|
||||||
|
max-width: 860px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: max(50vh - 340px, 50px) 0;
|
||||||
|
|
||||||
|
> .verticalBox {
|
||||||
|
position: relative;
|
||||||
|
height: 680px;
|
||||||
|
border: 1px solid #f2f4f7;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||||
|
|
||||||
|
> .buttonBack,
|
||||||
|
> .buttonNext {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .buttonBack {
|
||||||
|
left: 30px;
|
||||||
|
color: #98a2b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .buttonNext {
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared with child components
|
||||||
|
.primaryText,
|
||||||
|
.secondaryText {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryText {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondaryText {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.startButton {
|
||||||
|
width: 128px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 0 12px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 360px;
|
||||||
|
height: 44px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.input {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledOnboardingPage;
|
||||||
Reference in New Issue
Block a user