Commit 2eb83f0d authored by insert's avatar insert

Work on settings, add logout modal.

Co-authored-by: default avatarnizune <[email protected]>
parent 4cda850f
Pipeline #269 failed with stage
in 1 minute and 51 seconds
Subproject commit ffd110b9c8a789818019dc928183365b578b4594
Subproject commit 9f168e0b9c50d0a910b8a3479f548f0c364ce026
......@@ -4,11 +4,12 @@
top: 0;
width: 100%;
height: 100%;
z-index: 1;
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
color: whitesmoke;
.root {
margin: 0 10px;
......@@ -20,7 +21,7 @@
.container {
padding: 1em;
border-radius: 5px 5px 0 0;
background: var(--main);
background: var(--primary);
}
.title {
......@@ -31,7 +32,7 @@
.footer {
padding: 1em;
border-radius: 0 0 5px 5px;
background: var(--sub);
background: var(--secondary);
display: flex;
flex-direction: row-reverse;
......
......@@ -30,6 +30,8 @@ import { ReactComponent as fileRegular } from 'boxicons/svg/regular/bx-file.svg'
import { ReactComponent as logoutRegular } from 'boxicons/svg/regular/bx-log-out.svg';
import { ReactComponent as leftArrowAltRegular } from 'boxicons/svg/regular/bx-left-arrow-alt.svg';
import { ReactComponent as xRegular } from 'boxicons/svg/regular/bx-x.svg';
import { ReactComponent as checkRegular } from 'boxicons/svg/regular/bx-check.svg';
export type Icons = 'cogSolid' | 'plusRegular' | 'homeSolid' | 'newsSolid' | 'userDetailSolid'
......@@ -37,7 +39,7 @@ export type Icons = 'cogSolid' | 'plusRegular' | 'homeSolid' | 'newsSolid' | 'us
| 'idCardSolid' | 'extensionSolid' | 'microphoneSolid' | 'cardSolid' | 'brushSolid'
| 'bodyRegular' | 'slideshowSolid' | 'globeRegular' | 'wrenchSolid' | 'infoCircleSolid'
| 'fileRegular' | 'helpCircleSolid' | 'megaphoneSolid' | 'logoutRegular' | 'leftArrowAltRegular'
| 'xRegular';
| 'xRegular' | 'checkRegular';
const INDEX: { [key in Icons]: FunctionComponent } = { cogSolid, plusRegular, homeSolid, newsSolid,
userDetailSolid, userPlusSolid, mobileRegular,
chevronDownRegular, shieldSolid, idCardSolid,
......@@ -45,7 +47,8 @@ const INDEX: { [key in Icons]: FunctionComponent } = { cogSolid, plusRegular, h
brushSolid, bodyRegular, slideshowSolid,
globeRegular, wrenchSolid, infoCircleSolid,
fileRegular, helpCircleSolid, megaphoneSolid,
logoutRegular, leftArrowAltRegular, xRegular };
logoutRegular, leftArrowAltRegular, xRegular,
checkRegular };
interface IconProps extends SVGProps<SVGElement> {
icon: Icons,
color?: string,
......
......@@ -13,11 +13,13 @@ export var Instance: {
} = CreateInstance();
export function ResetClient() {
// ! REPLACE WITH CLOSE LATER
let c = Instance.client as any;
if (c.ws) c.ws.close();
clearInterval(c.pingPong);
Instance.loggedIn = false;
Instance.client.close();
delete Instance.client;
Instance = CreateInstance();
}
export function LogoutClient() {
ResetClient();
localStorage.removeItem('accessToken');
}
\ No newline at end of file
......@@ -28,11 +28,13 @@ export default function Login() {
let [ username, setUsername ] = useState('');
let [ password, setPassword ] = useState('');
let [ acceptTOS, setTOS ] = useState('');
let [ disableButton, setDisabled ] = useState(false);
let [ animation, playAnimation ] = useAnimator(Animation.BOUNCE_IN, 250);
function submitForm(e: React.FormEvent, setPage: (page: Page) => void) {
e.preventDefault();
setDisabled(true);
Instance.client.login(email, password).then((tfa) => {
if (tfa) {
......@@ -41,7 +43,10 @@ export default function Login() {
localStorage.setItem('accessToken', Instance.client.accessToken as string);
setPage(Page.LOAD);
}).catch((err: ErrorObject) => {console.log(err);setError(err)});
}).catch((err: ErrorObject) => {
setDisabled(false);
setError(err)
});
}
function toggle(target: LoginState, e?: React.MouseEvent) {
......@@ -68,7 +73,7 @@ export default function Login() {
</span>
<Input type="password" aria-label="Password" value={password} onChange={e => setPassword(e.target.value)} />
<a className={styles.link} href="/forgot" onClick={e => toggle(LoginState.ResetPassword, e)}>Forgot your password?</a>
<Button theme="confirm" type="submit" value="Log in" fullwidth={true} />
<Button theme="confirm" type="submit" value={ disableButton ? 'Logging you in...' : 'Log in' } fullwidth={true} disabled={disableButton} />
<span className={styles.signin}>Need an account? <a href="/register" className={styles.link} onClick={e => toggle(LoginState.Register, e)}>Sign up</a></span>
</div>;
......@@ -85,7 +90,7 @@ export default function Login() {
<Checkbox required value={acceptTOS} onChange={e => setTOS(e.target.value)}>
I agree to Riot's <a href="/somewhere/idk" target="_blank" className={styles.link}>Terms of Service</a> and <a href="/test" className={styles.link}>Community Guidelines</a>
</Checkbox>
<Button theme="confirm" type="submit" value="Sign up" fullwidth={true} />
<Button theme="confirm" type="submit" value="Sign up" fullwidth={true} disabled={disableButton} />
<span className={styles.signin}>Have an account? <a href="/login" className={styles.link} onClick={e => toggle(LoginState.Login, e)}>Sign in</a></span>
</div>;
......@@ -95,7 +100,7 @@ export default function Login() {
<div className={styles.welcome}>Two-Factor Authentication</div>
<NumberGroup aria-label="Verification Code" digits={6} separator={3} />
<a href="/login" className={styles.link} onClick={e => toggle(LoginState.Login, e)}>Resend Code</a>
<Button theme="confirm" type="submit" value="Log in" fullwidth={true} />
<Button theme="confirm" type="submit" value="Log in" fullwidth={true} disabled={disableButton} />
<Button theme="cancel" type="submit" value="Cancel" onClick={e => toggle(LoginState.Login, e)} fullwidth={true} />
</div>;
break;
......@@ -105,7 +110,7 @@ export default function Login() {
<div className={styles.text}>No worries, just enter your e-mail and we'll send you instructions to reset your password.</div>
<span className={styles.title}>Email</span>
<Input type="email" name="email" value={email} onChange={e => setEmail(e.target.value)} />
<Button theme="confirm" type="submit" value="Send E-Mail" fullwidth={true} />
<Button theme="confirm" type="submit" value="Send E-Mail" fullwidth={true} disabled={disableButton} />
<span className={styles.signin}>Remembered your password? <a href="/register" className={styles.link} onClick={e => toggle(LoginState.Login, e)}>Log in</a></span>
</div>;
break;
......
......@@ -22,6 +22,7 @@
padding: 80px 40px 40px;
@media screen and (max-width: 900px) {
background: var(--primary);
padding: 20px 12px;
&:not(.shown) {
display: none;
......@@ -75,8 +76,10 @@
}
.close {
display: none;
@media screen and (min-width: 901px) {
display: block;
min-width: 140px;
svg {
right: 100px;
position: absolute;
display: flex;
......@@ -88,4 +91,8 @@
border: 1px solid grey;
border-radius: 50%;
}
@media screen and (max-width: 901px) {
display: none;
}
}
\ No newline at end of file
......@@ -8,6 +8,8 @@ import { scrollable } from '../components/util/Scrollbar';
import { Icon } from '../components/ui/elements/Icon';
import { AppContext, Page as AppPage } from '../App';
import { RenderPage } from './settings/pages';
import Modal from '../components/ui/components/Modal';
import { Instance, LogoutClient } from '../internal/Client';
export enum Page {
ACCOUNT,
......@@ -53,18 +55,21 @@ export const SettingsContext = createContext<{
tab: Page,
setTab: (tab: Page) => void,
showContent: boolean,
setShown: (show: boolean) => void
setShown: (show: boolean) => void,
setLogoutModal: (show: boolean) => void
}>({
tab: Page.ACCOUNT,
setTab: () => {},
showContent: false,
setShown: () => {}
setShown: () => {},
setLogoutModal: () => {}
});
export function Settings() {
let app = useContext(AppContext);
let [ tab, setTab ] = useState(Page.ACCOUNT);
let [ showContent, setShown ] = useState(false);
let [ showLogout, setLogoutModal ] = useState(false);
let content = classNames({
[styles.content]: true,
......@@ -74,7 +79,8 @@ export function Settings() {
const states = {
tab, setTab,
showContent, setShown
showContent, setShown,
setLogoutModal
} as any;
function doClose() {
......@@ -85,6 +91,11 @@ export function Settings() {
}
}
function doLogout() {
app.setPage(AppPage.LOGIN);
LogoutClient();
}
let doHideTitle: CSSProperties = {
display: (tab === Page.ACCOUNT ||
tab === Page.PRO) ?
......@@ -101,7 +112,7 @@ export function Settings() {
{ showContent ? <Icon className={styles.x} icon="leftArrowAltRegular" onClick={doClose} />
: <Icon className={styles.x} icon="xRegular" onClick={doClose} /> }
<span className={styles.title}>{showContent ? PageTitles[tab] : 'Settings'}</span>
<Icon icon="logoutRegular" onClick={() => alert('no leaving this place')}/>
<Icon icon="logoutRegular" onClick={() => setLogoutModal(true)}/>
</div>
<div className={styles.main}>
<SettingsSidebar />
......@@ -110,9 +121,28 @@ export function Settings() {
<div className={styles.title} style={doHideTitle}>{PageTitles[tab]}</div>
{RenderPage(tab)}
</div>
<Icon className={styles.close} icon="xRegular" onClick={doClose} />
<div className={styles.close}>
<Icon icon="xRegular" onClick={doClose} />
</div>
</div>
</div>
{ showLogout && <Modal
title='Are you sure?'
buttons={[
{
type: 'warning',
value: 'Logout',
handler: doLogout
},
{
close: true,
value: 'Cancel'
}
]}
dismiss={() => setLogoutModal(false)}
allowClose={true}>
You will be logged out of your RIOT account.
</Modal> }
</div>
</SettingsContext.Provider>
);
......
......@@ -27,17 +27,19 @@ export const SettingsSidebar = memo(() => {
return <span className={classes}>{ props.type }</span>;
}
function Tab(props: { icon: Icons, for?: Page, custom?: boolean, children?: ReactNode[] | ReactNode, beta?: boolean, classes?: any }) {
function Tab(props: { icon: Icons, for?: Page, custom?: boolean, children?: ReactNode[] | ReactNode, beta?: boolean, classes?: any, canHide?: boolean, onClick?: Function }) {
let classes = classNames(props.classes, {
[styles.tab]: true,
[styles.canHide]: props.for === Page.ACCOUNT
[styles.canHide]: props.canHide
});
let name = typeof props.for === 'undefined' ? undefined : PageTitles[props.for];
return (
<div className={classes} aria-label={name} onClick={() => {
props.onClick && props.onClick();
if (typeof props.for === 'undefined') return;
settings.setShown(true);
typeof props.for !== 'undefined' && settings.setTab(props.for);
settings.setTab(props.for);
}}>
<Icon icon={props.icon} />
{props.custom ? props.children : name}
......@@ -67,9 +69,9 @@ export const SettingsSidebar = memo(() => {
</div>
</div>
<Section title="User Settings">
<Tab icon="idCardSolid" for={Page.ACCOUNT} />
<Tab icon="idCardSolid" for={Page.ACCOUNT} canHide />
<Tab icon="shieldSolid" for={Page.APPS} />
<Tab icon="extensionSolid" for={Page.INTEGRATIONS} beta/>
<Tab icon="extensionSolid" for={Page.INTEGRATIONS} beta />
</Section>
<Section title="Riot PRO">
<Tab icon="idCardSolid" classes={styles.pro} for={Page.PRO} custom>
......@@ -92,7 +94,7 @@ export const SettingsSidebar = memo(() => {
<Tab icon="helpCircleSolid" for={Page.SUPPORT} />
<Tab icon="megaphoneSolid" for={Page.FEEDBACK} />
</Section>
<Tab icon="logoutRegular" classes={styles.logoutButton} custom>
<Tab icon="logoutRegular" classes={styles.logoutButton} onClick={() => settings.setLogoutModal(true)} custom canHide>
Logout
</Tab>
<div className={styles.branding}>
......
.panel {
color: var(--body-text);
}
.themePicker {
max-width: 650px;
display: flex;
box-sizing: border-box;
.type {
display: block;
color: grey;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.theme {
flex: 1 1 auto;
display: inline-block;
margin: 0 8px;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
img {
width: 100%;
height: auto;
border-radius: 8px;
border: 2px solid transparent;
cursor: pointer;
transition: border .3s;
//pointer-events: none;
&:hover, &.active {
border: 2px solid var(--accent-color, mediumslatepurple);
}
&.active {
cursor: default;
}
}
}
}
.colorPicker {
display: flex;
.customColor {
position: relative;
height: 64px;
width: 64px;
padding: 0;
border: none;
border-radius: 3px;
cursor: pointer;
outline: 0;
margin-right: 6px;
.check, .edit {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 61px;
width: 61px;
transition: opacity 0.3s;
}
.check { opacity: 1; }
.edit { opacity: 0; }
&:hover {
.check { opacity: 0; }
.edit { opacity: 1; }
}
}
.colorGrid {
display: grid;
grid-gap: 6px;
.check {
height: 25px;
width: 25px;
fill: white;
}
.color {
padding: 0;
height: 25px;
width: 25px;
border: none;
border-radius: 3px;
cursor: pointer;
outline: 0;
border: 2px solid transparent;
transition: border .3s;
&:hover, &.selected { border: 2px solid var(--icon); }
}
}
}
\ No newline at end of file
import React from 'react';
import { Icon } from '../../../../components/ui/elements/Icon';
import main from '../index.module.scss';
import styles from './Appearance.module.scss';
import lightTheme from '../../../../assets/images/light.svg';
import darkTheme from '../../../../assets/images/dark.svg';
export default function Appearance() {
return (
<div>
</div>
<div className={styles.panel}>
<div className={main.section}>
<div className={main.category}>Theme</div>
<div className={styles.themePicker}>
<div className={styles.theme}>
<div className={styles.light}>
<img alt="Light Mode" src={lightTheme} draggable={false}/>
</div>
<span className={styles.type}>Light</span>
</div>
<div className={styles.theme}>
<div className={styles.dark}>
<img alt="Dark Mode" src={darkTheme} draggable={false}/>
</div>
<span className={styles.type}>Dark</span>
</div>
</div>
</div>
<div className={main.section}>
<div className={main.category}>Accent Color</div>
<div className={styles.colorPicker}>
<a className={styles.customColor}>
<Icon className={styles.check} icon="checkRegular"/>
<Icon className={styles.edit} icon="brushSolid"/>
</a>
<div className={`${styles.colorGrid} ${styles.disabled}`}>
<a className={styles.color}>
<Icon className={styles.check} icon="checkRegular"/>
</a>
</div>
</div>
</div>
<div className={main.section}>
<div className={main.category}>Message Display</div>
{/*<RadioGroup>
<Checkbox type="radio" text="Default" description="Beautiful, sleek and modern."/>
<Checkbox type="radio" text="Compact" description="Long live the IRC."/>
</RadioGroup>*/}
</div>
<div className={main.section}>
<div className={main.category}>Sync Options</div>
{/*<Checkbox
type="toggle"
checked={true}
text="Sync my preferences"
description="Synchornize my appearance settings across my devices." /> */}
</div>
</div>
);
}
\ No newline at end of file
......@@ -6,8 +6,8 @@
.pfp {
position: relative;
display: flex;
height: 100px;
width: 100px;
height: 80px;
width: 80px;
background: #434343;
border-radius: 50%;
flex: 0 0 auto;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment