mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
start implementing proper settings modal
This commit is contained in:
parent
34db3f9561
commit
1750b22af7
@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- Primary Meta Tags -->
|
<!-- Primary Meta Tags -->
|
||||||
|
26
src/components/SectionTitle.tsx
Normal file
26
src/components/SectionTitle.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Text = styled.h2`
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
function SectionTitle({ children }: React.PropsWithChildren<Props>) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text>{children}</Text>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SectionTitle;
|
@ -27,6 +27,7 @@ interface ModalProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
withEmptyActionBar?: boolean;
|
withEmptyActionBar?: boolean;
|
||||||
withoutCloseButton?: boolean;
|
withoutCloseButton?: boolean;
|
||||||
|
fullScreen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +49,7 @@ export const ModalBase = styled.div<{ closing?: boolean }>`
|
|||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
overflow-y: auto;
|
overflow: hidden;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@ -72,12 +73,8 @@ export const ModalBase = styled.div<{ closing?: boolean }>`
|
|||||||
* Wrapper for modal content, handles the sizing and positioning
|
* Wrapper for modal content, handles the sizing and positioning
|
||||||
*/
|
*/
|
||||||
export const ModalWrapper = styled.div<
|
export const ModalWrapper = styled.div<
|
||||||
Pick<ModalProps, "transparent" | "maxWidth" | "maxHeight"> & { actions: boolean }
|
Pick<ModalProps, "transparent" | "maxWidth" | "maxHeight"> & { actions: boolean; fullScreen?: boolean }
|
||||||
>`
|
>`
|
||||||
min-height: 200px;
|
|
||||||
max-width: min(calc(100vw - 20px), ${(props) => props.maxWidth ?? "450px"});
|
|
||||||
max-height: min(calc(100vh - 20px), ${(props) => props.maxHeight ?? "650px"});
|
|
||||||
|
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -86,17 +83,28 @@ export const ModalWrapper = styled.div<
|
|||||||
animation-duration: 0.25s;
|
animation-duration: 0.25s;
|
||||||
animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1);
|
animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--background-tertiary);
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
!props.maxWidth &&
|
!props.fullScreen &&
|
||||||
|
css`
|
||||||
|
min-height: 200px;
|
||||||
|
max-width: min(calc(100vw - 20px), ${props.maxWidth ?? "450px"});
|
||||||
|
max-height: min(calc(100vh - 20px), ${props.maxHeight ?? "650px"});
|
||||||
|
`}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.fullScreen &&
|
||||||
css`
|
css`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
!props.transparent &&
|
!props.transparent &&
|
||||||
|
!props.fullScreen &&
|
||||||
css`
|
css`
|
||||||
overflow: hidden;
|
|
||||||
background: var(--background-primary);
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
@ -122,12 +130,6 @@ export const ModalContentContainer = styled.div<Pick<ModalProps, "transparent" |
|
|||||||
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
|
|
||||||
${(props) =>
|
|
||||||
!props.transparent &&
|
|
||||||
css`
|
|
||||||
background: var(--background-primary);
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Actions = styled.div`
|
const Actions = styled.div`
|
||||||
|
@ -1,105 +1,264 @@
|
|||||||
import { FormControlLabel, FormGroup, Switch } from "@mui/material";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import styled from "styled-components";
|
import { useState } from "react";
|
||||||
|
import styled, { css } from "styled-components";
|
||||||
import { ModalProps } from "../../controllers/modals";
|
import { ModalProps } from "../../controllers/modals";
|
||||||
import { useAppStore } from "../../stores/AppStore";
|
import { useAppStore } from "../../stores/AppStore";
|
||||||
import { isTauri } from "../../utils/Utils";
|
import { isTauri } from "../../utils/Utils";
|
||||||
import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
|
import { APP_VERSION, GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
|
||||||
import Button from "../Button";
|
import Icon from "../Icon";
|
||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
import { Modal } from "./ModalComponents";
|
import { Modal } from "./ModalComponents";
|
||||||
|
import AccountSettingsPage from "./SettingsPages/AccountSettingsPage";
|
||||||
|
import DeveloperSettingsPage from "./SettingsPages/DeveloperSettingsPage";
|
||||||
|
import ExperimentsPage from "./SettingsPages/ExperimentsPage";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const SidebarView = styled.div`
|
||||||
padding: 16px 0;
|
display: flex;
|
||||||
gap: 8px;
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Sidebar = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 220px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SidebarInner = styled.div`
|
||||||
|
overflow: hidden scroll;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: var(--background-secondary);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SidebarNav = styled.nav`
|
||||||
|
width: 220px;
|
||||||
|
padding: 60px 6px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SidebarNavWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ActionWrapper = styled.div`
|
const Content = styled.div`
|
||||||
margin-top: 20px;
|
|
||||||
gap: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 800px;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: var(--background-primary);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const VersionWrapper = styled.div`
|
const ContentInner = styled.div`
|
||||||
|
overflow: hidden scroll;
|
||||||
|
justify-content: flex-start;
|
||||||
|
position: static;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
user-select: text;
|
align-items: flex-start;
|
||||||
|
background: var(--background-primary);
|
||||||
|
box-sizing: border-box;
|
||||||
|
`;
|
||||||
|
|
||||||
& > span {
|
const ContentColumn = styled.div`
|
||||||
|
padding: 60px 40px 80px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 740px;
|
||||||
|
min-width: 460px;
|
||||||
|
min-height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
padding: 6px 10px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Item = styled.div<{ selected?: boolean; textColor?: string }>`
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: ${(props) => props.textColor ?? "var(--text-secondary);"};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: hsl(var(--background-primary-hsl) / 0.6);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.selected &&
|
||||||
|
css`
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
color: var(--text);
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
margin: 8px 10px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--text-disabled);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VersionInfo = styled.div`
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseContainer = styled.div`
|
||||||
|
margin-right: 20px;
|
||||||
|
flex: 0 0 36px;
|
||||||
|
width: 60px;
|
||||||
|
padding-top: 60px;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseContainerInner = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseContainerWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CloseButtonWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 40px;
|
||||||
|
border: solid 1px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-secondary);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsModal = observer(({ ...props }: ModalProps<"settings">) => {
|
export const SettingsModal = observer(({ ...props }: ModalProps<"settings">) => {
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
|
||||||
|
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const value = e.currentTarget.getAttribute("data-value");
|
||||||
|
if (value) {
|
||||||
|
setIndex(parseInt(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal {...props}>
|
<Modal {...props} fullScreen withoutCloseButton withEmptyActionBar padding="0">
|
||||||
<Wrapper>
|
<SidebarView>
|
||||||
<FormGroup>
|
<Sidebar>
|
||||||
<FormControlLabel
|
<SidebarInner>
|
||||||
control={<Switch checked={app.fpsShown} onChange={(e) => app.setFpsShown(e.target.checked)} />}
|
<SidebarNav>
|
||||||
label="Show FPS Graph"
|
<SidebarNavWrapper>
|
||||||
/>
|
<Header>User Settings</Header>
|
||||||
</FormGroup>
|
<Item data-value="0" onClick={onClick}>
|
||||||
|
Account
|
||||||
{isTauri && (
|
</Item>
|
||||||
<FormGroup>
|
<Divider />
|
||||||
<FormControlLabel
|
<Item data-value="1" onClick={onClick}>
|
||||||
control={
|
Developer Options
|
||||||
<Switch
|
</Item>
|
||||||
checked={app.updaterStore?.enabled}
|
<Item data-value="2" onClick={onClick}>
|
||||||
onChange={(e) => app.updaterStore?.setEnabled(e.target.checked)}
|
Experiments
|
||||||
/>
|
</Item>
|
||||||
}
|
<Divider />
|
||||||
label="Enabled auto updater"
|
<Item onClick={app.logout}>
|
||||||
/>
|
<div
|
||||||
</FormGroup>
|
style={{
|
||||||
)}
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
<VersionWrapper>
|
alignItems: "center",
|
||||||
|
color: "var(--error)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Log Out
|
||||||
|
<Icon icon="mdiLogout" size="16px" color="var(--error)" />
|
||||||
|
</div>
|
||||||
|
</Item>
|
||||||
|
<Divider />
|
||||||
|
<VersionInfo>
|
||||||
<span>
|
<span>
|
||||||
Client Version:{" "}
|
{GIT_BRANCH} {APP_VERSION} (
|
||||||
<Link href={`${REPO_URL}/commit/${GIT_REVISION}`} target="_blank" rel="noreferrer">
|
|
||||||
{GIT_REVISION.substring(0, 7)}
|
|
||||||
</Link>
|
|
||||||
{` `}
|
|
||||||
<Link
|
<Link
|
||||||
href={GIT_BRANCH !== "DETACHED" ? `${REPO_URL}/tree/${GIT_BRANCH}` : undefined}
|
href={`${REPO_URL}/commit/${GIT_REVISION}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
({GIT_BRANCH})
|
{GIT_REVISION.substring(0, 7)}
|
||||||
</Link>
|
</Link>
|
||||||
|
)
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{isTauri && (
|
{isTauri && (
|
||||||
<>
|
<>
|
||||||
<span>App Version: {window.globals.appVersion ?? "Fetching version information..."}</span>
|
{/* <span>
|
||||||
|
{window.globals.appVersion
|
||||||
|
? `${window.globals.appVersion} (${(
|
||||||
|
<Link
|
||||||
|
href={`${REPO_URL}/commit/${GIT_REVISION}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{GIT_REVISION.substring(0, 7)}
|
||||||
|
</Link>
|
||||||
|
)})`
|
||||||
|
: "Fetching version information..."}
|
||||||
|
</span> */}
|
||||||
<span>
|
<span>
|
||||||
Tauri Version: {window.globals.tauriVersion ?? "Fetching version information..."}
|
Tauri {window.globals.tauriVersion ?? "Fetching version information..."}
|
||||||
</span>
|
</span>
|
||||||
<span>Platform: {window.globals.platform.name}</span>
|
<span>{`${window.globals.platform.name} ${window.globals.platform.arch} (${window.globals.platform.version})`}</span>
|
||||||
<span>Arch: {window.globals.platform.arch}</span>
|
<span>{window.globals.platform.locale ?? "Unknown"}</span>
|
||||||
<span>OS Version: {window.globals.platform.version}</span>
|
|
||||||
<span>Locale: {window.globals.platform.locale ?? "Unknown"}</span>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</VersionWrapper>
|
</VersionInfo>
|
||||||
|
</SidebarNavWrapper>
|
||||||
<ActionWrapper>
|
</SidebarNav>
|
||||||
<Button
|
</SidebarInner>
|
||||||
palette="danger"
|
</Sidebar>
|
||||||
|
<Content>
|
||||||
|
<ContentInner>
|
||||||
|
<ContentColumn>
|
||||||
|
{index === 0 && <AccountSettingsPage />}
|
||||||
|
{index === 1 && <DeveloperSettingsPage />}
|
||||||
|
{index === 2 && <ExperimentsPage />}
|
||||||
|
</ContentColumn>
|
||||||
|
<CloseContainer>
|
||||||
|
<CloseContainerInner></CloseContainerInner>
|
||||||
|
<CloseContainerWrapper>
|
||||||
|
<CloseButtonWrapper
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
app.logout();
|
console.log("Close modal");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Logout
|
<Icon icon="mdiClose" size="18px" />
|
||||||
</Button>
|
</CloseButtonWrapper>
|
||||||
</ActionWrapper>
|
</CloseContainerWrapper>
|
||||||
</Wrapper>
|
</CloseContainer>
|
||||||
|
</ContentInner>
|
||||||
|
</Content>
|
||||||
|
</SidebarView>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
146
src/components/modals/SettingsPages/AccountSettingsPage.tsx
Normal file
146
src/components/modals/SettingsPages/AccountSettingsPage.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useState } from "react";
|
||||||
|
import styled, { css } from "styled-components";
|
||||||
|
import { useAppStore } from "../../../stores/AppStore";
|
||||||
|
import SectionTitle from "../../SectionTitle";
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UserInfoContainer = styled.div`
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
padding: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Field = styled.div<{ spacerTop?: boolean; spacerBottom?: boolean }>`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.spacerTop &&
|
||||||
|
css`
|
||||||
|
margin-top: 24px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.spacerBottom &&
|
||||||
|
css`
|
||||||
|
margin-bottom: 24px;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FieldTitle = styled.span`
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FieldValue = styled.div`
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FieldValueText = styled.span`
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FieldValueToggle = styled.button`
|
||||||
|
color: var(--text-link);
|
||||||
|
cursor: pointer;
|
||||||
|
width: auto;
|
||||||
|
display: inline;
|
||||||
|
height: auto;
|
||||||
|
padding: 2px 4px;
|
||||||
|
position: relative;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
user-select: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function AccountSettingsPage() {
|
||||||
|
const app = useAppStore();
|
||||||
|
const [shouldRedactEmail, setShouldRedactEmail] = useState(true);
|
||||||
|
|
||||||
|
const redactEmail = (email: string) => {
|
||||||
|
const [username, domain] = email.split("@");
|
||||||
|
return `${"*".repeat(username.length)}@${domain}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const refactPhoneNumber = (phoneNumber: string) => {
|
||||||
|
const lastFour = phoneNumber.slice(-4);
|
||||||
|
return "*".repeat(phoneNumber.length - 4) + lastFour;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Account</SectionTitle>
|
||||||
|
<Content>
|
||||||
|
<UserInfoContainer>
|
||||||
|
<Field spacerBottom>
|
||||||
|
<Row>
|
||||||
|
<FieldTitle>Username</FieldTitle>
|
||||||
|
|
||||||
|
<FieldValue>
|
||||||
|
<FieldValueText>
|
||||||
|
{app.account?.username}#{app.account?.discriminator}
|
||||||
|
</FieldValueText>
|
||||||
|
</FieldValue>
|
||||||
|
</Row>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field>
|
||||||
|
<Row>
|
||||||
|
<FieldTitle>Email</FieldTitle>
|
||||||
|
|
||||||
|
<FieldValue>
|
||||||
|
<FieldValueText>
|
||||||
|
{app.account?.email
|
||||||
|
? shouldRedactEmail
|
||||||
|
? redactEmail(app.account.email)
|
||||||
|
: app.account.email
|
||||||
|
: "No email added."}
|
||||||
|
|
||||||
|
<FieldValueToggle onClick={() => setShouldRedactEmail(!shouldRedactEmail)}>
|
||||||
|
{shouldRedactEmail ? "Reveal" : "Hide"}
|
||||||
|
</FieldValueToggle>
|
||||||
|
</FieldValueText>
|
||||||
|
</FieldValue>
|
||||||
|
</Row>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field spacerTop>
|
||||||
|
<Row>
|
||||||
|
<FieldTitle>Phone Number</FieldTitle>
|
||||||
|
|
||||||
|
<FieldValue>
|
||||||
|
<FieldValueText>No phone number added.</FieldValueText>
|
||||||
|
</FieldValue>
|
||||||
|
</Row>
|
||||||
|
</Field>
|
||||||
|
</UserInfoContainer>
|
||||||
|
</Content>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(AccountSettingsPage);
|
@ -0,0 +1,19 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import SectionTitle from "../../SectionTitle";
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function DeveloperSettingsPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Developer Options</SectionTitle>
|
||||||
|
<Content></Content>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(DeveloperSettingsPage);
|
118
src/components/modals/SettingsPages/ExperimentsPage.tsx
Normal file
118
src/components/modals/SettingsPages/ExperimentsPage.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { useAppStore } from "../../../stores/AppStore";
|
||||||
|
import { EXPERIMENT_LIST, Experiment as ExperimentType } from "../../../stores/ExperimentsStore";
|
||||||
|
import SectionTitle from "../../SectionTitle";
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ExperimentList = styled.ul`
|
||||||
|
display: grid;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Experiment = styled.li`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.span`
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--text);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Subtitle = styled.div`
|
||||||
|
color: var(--text-disabled);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const OverrideText = styled.div`
|
||||||
|
color: var(--text.muted);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Select = styled.select`
|
||||||
|
appearance: none;
|
||||||
|
/* safari */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: var(--background-tertiary);
|
||||||
|
border-color: var(--background-tertiary);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 8px 8px 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function ExperimentItem({ experiment }: { experiment: ExperimentType }) {
|
||||||
|
const app = useAppStore();
|
||||||
|
const isActive = app.experiments.isExperimentEnabled(experiment.id);
|
||||||
|
const activeTreatment = app.experiments.getTreatment(experiment.id);
|
||||||
|
const [isExpanded, setExpanded] = useState(isActive);
|
||||||
|
|
||||||
|
const toggle = () => setExpanded(!isExpanded);
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const value = Number.parseInt(e.target.value);
|
||||||
|
app.experiments.setTreatment(experiment.id, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Experiment key={experiment.id}>
|
||||||
|
<div style={{ marginBottom: "10px", cursor: "pointer" }} onClick={toggle}>
|
||||||
|
<Title>{experiment.name}</Title>
|
||||||
|
<Subtitle>{experiment.description}</Subtitle>
|
||||||
|
</div>
|
||||||
|
{isExpanded && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<OverrideText>Treatment Override</OverrideText>
|
||||||
|
<Select onChange={onChange}>
|
||||||
|
{experiment.treatments.map((treatment) => (
|
||||||
|
<option
|
||||||
|
key={treatment.id}
|
||||||
|
value={treatment.id}
|
||||||
|
selected={(!isActive && treatment.id === 0) || activeTreatment?.id === treatment.id}
|
||||||
|
>
|
||||||
|
{`${treatment.name}${treatment.description ? ": " + treatment.description : ""}`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Experiment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExperimentsPage() {
|
||||||
|
const app = useAppStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Experiments</SectionTitle>
|
||||||
|
<Content>
|
||||||
|
<ExperimentList>
|
||||||
|
{EXPERIMENT_LIST.map((experiment) => (
|
||||||
|
<ExperimentItem experiment={experiment} />
|
||||||
|
))}
|
||||||
|
</ExperimentList>
|
||||||
|
</Content>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(ExperimentsPage);
|
@ -1,6 +1,6 @@
|
|||||||
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
|
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
|
||||||
|
|
||||||
export type ExperimentType = "test" | "message_queue";
|
export type ExperimentType = "test" | "message_queue" | "presence_rings";
|
||||||
|
|
||||||
export interface ExperimentTreatment {
|
export interface ExperimentTreatment {
|
||||||
id: number;
|
id: number;
|
||||||
@ -56,6 +56,27 @@ export const EXPERIMENT_LIST: Experiment[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "presence_rings",
|
||||||
|
name: "Presence Rings",
|
||||||
|
description: "Use rings for presence status instead of dots",
|
||||||
|
treatments: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: "Control",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Treatment 1",
|
||||||
|
description: "Use presence dots",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Treatment 2",
|
||||||
|
description: "Use presence rings",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
@ -76,7 +97,7 @@ export default class ExperimentsStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
getTreatment(id: ExperimentType) {
|
getTreatment(id: ExperimentType): ExperimentTreatment | undefined {
|
||||||
const treatment = this.experiments.get(id);
|
const treatment = this.experiments.get(id);
|
||||||
const experiment = EXPERIMENT_LIST.find((x) => x.id === id);
|
const experiment = EXPERIMENT_LIST.find((x) => x.id === id);
|
||||||
return experiment?.treatments.find((x) => x.id === treatment);
|
return experiment?.treatments.find((x) => x.id === treatment);
|
||||||
|
Loading…
Reference in New Issue
Block a user