Adds AccountModal component

This commit is contained in:
Justin Edmund 2022-03-04 05:17:54 -08:00
parent 053522a81d
commit 4e701572ab
2 changed files with 318 additions and 0 deletions

View file

@ -0,0 +1,160 @@
.Account.Dialog {
display: flex;
flex-direction: column;
gap: $unit * 2;
width: $unit * 60;
form {
display: flex;
flex-direction: column;
gap: $unit * 2;
.Switch {
$height: 34px;
background: $grey-70;
border-radius: $height / 2;
border: none;
position: relative;
width: 58px;
height: $height;
&:focus {
box-shadow: 0 0 0 2px $grey-00;
}
&[data-state="checked"] {
background: $grey-00;
}
}
.Thumb {
background: white;
border-radius: 13px;
display: block;
height: 26px;
width: 26px;
transition: transform 100ms;
transform: translateX(-1px);
&:hover {
cursor: pointer;
}
&[data-state="checked"] {
background: white;
transform: translateX(21px);
}
}
.Button {
font-size: $font-regular;
padding: ($unit * 1.5) ($unit * 2);
margin-top: $unit * 2;
width: 100%;
&.btn-disabled {
background: $grey-90;
color: $grey-70;
cursor: not-allowed;
}
&:not(.btn-disabled) {
background: $grey-90;
color: $grey-40;
&:hover {
background: $grey-80;
}
}
}
.field {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
select {
background: no-repeat url('/icons/ArrowDark.svg'), $grey-90;
background-position-y: center;
background-position-x: 95%;
margin: 0;
width: 240px;
}
.left {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: $unit / 2;
label {
color: $grey-00;
font-size: $font-regular;
}
p {
color: $grey-60;
font-size: $font-small;
line-height: 1.1;
max-width: 300px;
}
}
.preview {
$diameter: 48px;
background-color: $grey-90;
border-radius: 999px;
height: $diameter;
width: $diameter;
img {
height: $diameter;
width: $diameter;
}
&.fire {
background: $fire-bg-light;
}
&.water {
background: $water-bg-light;
}
&.wind {
background: $wind-bg-light;
}
&.earth {
background: $earth-bg-light;
}
&.dark {
background: $dark-bg-light;
}
&.light {
background: $light-bg-light;
}
}
}
section {
margin-bottom: $unit;
h2 {
margin-bottom: $unit * 3;
}
}
}
.DialogDescription {
font-size: $font-regular;
line-height: 1.24;
margin-bottom: $unit;
&:last-of-type {
margin-bottom: 0;
}
}
}

View file

@ -0,0 +1,158 @@
import React, { useEffect, useState } from 'react'
import { useCookies } from 'react-cookie'
import { useSnapshot } from 'valtio'
import * as Dialog from '@radix-ui/react-dialog'
import * as Switch from '@radix-ui/react-switch'
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { pictureData } from '~utils/pictureData'
import Button from '~components/Button'
import CrossIcon from '~public/icons/Cross.svg'
import './index.scss'
const AccountModal = () => {
const { account } = useSnapshot(accountState)
// Cookies
const [cookies] = useCookies(['user'])
const headers = (cookies.user != null) ? {
headers: {
'Authorization': `Bearer ${cookies.user.access_token}`
}
} : {}
// State
const [open, setOpen] = useState(false)
const [picture, setPicture] = useState('')
const [language, setLanguage] = useState('')
const [privateProfile, setPrivateProfile] = useState(false)
// Refs
const pictureSelect = React.createRef<HTMLSelectElement>()
const languageSelect = React.createRef<HTMLSelectElement>()
const privateSelect = React.createRef<HTMLInputElement>()
useEffect(() => {
if (cookies.user) setPicture(cookies.user.picture)
if (cookies.user) setLanguage(cookies.user.language)
}, [cookies])
const pictureOptions = (
pictureData.sort((a, b) => (a.name.en > b.name.en) ? 1 : -1).map((item, i) => {
return (
<option key={`picture-${i}`} value={item.filename}>{item.name.en}</option>
)
})
)
function handlePictureChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (pictureSelect.current)
setPicture(pictureSelect.current.value)
}
function handleLanguageChange(event: React.ChangeEvent<HTMLSelectElement>) {
if (languageSelect.current)
setLanguage(languageSelect.current.value)
}
function handlePrivateChange(checked: boolean) {
setPrivateProfile(checked)
}
function update(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const object = {
user: {
picture: picture,
element: pictureData.find(i => i.filename === picture)?.element,
language: language,
private: privateProfile
}
}
api.endpoints.users.update(cookies.user.user_id, object, headers)
.then(response => {
setOpen(false)
})
}
function openChange(open: boolean) {
setOpen(open)
}
return (
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger asChild>
<li className="MenuItem">
<span>Settings</span>
</li>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Content className="Account Dialog" onOpenAutoFocus={ (event) => event.preventDefault() }>
<div className="DialogHeader">
<div className="DialogTop">
<Dialog.Title className="SubTitle">Account Settings</Dialog.Title>
<Dialog.Title className="DialogTitle">@{account.user?.username}</Dialog.Title>
</div>
<Dialog.Close className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</Dialog.Close>
</div>
<form onSubmit={update}>
<div className="field">
<div className="left">
<label>Picture</label>
</div>
<div className={`preview ${pictureData.find(i => i.filename === picture)?.element}`}>
<img
alt="Profile preview"
srcSet={`/profile/${picture}.png,
/profile/${picture}@2x.png 2x`}
src={`/profile/${picture}.png`}
/>
</div>
<select name="picture" onChange={handlePictureChange} value={picture} ref={pictureSelect}>
{pictureOptions}
</select>
</div>
<div className="field">
<div className="left">
<label>Language</label>
</div>
<select name="language" onChange={handleLanguageChange} value={language} ref={languageSelect}>
<option key="en" value="en">English</option>
<option key="jp" value="jp">Japanese</option>
</select>
</div>
<div className="field">
<div className="left">
<label>Private</label>
<p>Hide your profile and prevent your grids from showing up in collections</p>
</div>
<Switch.Root className="Switch" onCheckedChange={handlePrivateChange} checked={privateProfile}>
<Switch.Thumb className="Thumb" />
</Switch.Root>
</div>
<Button>Save settings</Button>
</form>
</Dialog.Content>
<Dialog.Overlay className="Overlay" />
</Dialog.Portal>
</Dialog.Root>
)
}
export default AccountModal