Merge pull request #1871 from SkynetLabs/dashboard-v2-profile-page
Dashboard V2 - settings pages
This commit is contained in:
commit
372aafdb45
|
@ -0,0 +1,35 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { useUser } from "../../contexts/user";
|
||||||
|
import { SimpleUploadIcon } from "../Icons";
|
||||||
|
|
||||||
|
const AVATAR_PLACEHOLDER = "/images/avatar-placeholder.svg";
|
||||||
|
|
||||||
|
export const AvatarUploader = (props) => {
|
||||||
|
const { user } = useUser();
|
||||||
|
const [imageUrl, setImageUrl] = useState(AVATAR_PLACEHOLDER);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageUrl(user.avatarUrl ?? AVATAR_PLACEHOLDER);
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<div
|
||||||
|
className={`flex justify-center items-center xl:w-[245px] xl:h-[245px] bg-contain bg-none xl:bg-[url(/images/avatar-bg.svg)]`}
|
||||||
|
>
|
||||||
|
<img src={imageUrl} className="w-[160px]" alt="" />
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-4 hover:underline decoration-1 decoration-dashed underline-offset-2 decoration-gray-400"
|
||||||
|
type="button"
|
||||||
|
onClick={console.info.bind(console)}
|
||||||
|
>
|
||||||
|
<SimpleUploadIcon size={20} className="shrink-0" /> Upload profile picture
|
||||||
|
</button>
|
||||||
|
{/* TODO: actual uploading */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./AvatarUploader";
|
|
@ -6,7 +6,7 @@ import styled from "styled-components";
|
||||||
*/
|
*/
|
||||||
export const Button = styled.button.attrs(({ $primary }) => ({
|
export const Button = styled.button.attrs(({ $primary }) => ({
|
||||||
type: "button",
|
type: "button",
|
||||||
className: `px-6 py-3 rounded-full font-sans uppercase text-xs tracking-wide text-palette-600 transition-[filter] hover:brightness-90
|
className: `px-6 py-2.5 rounded-full font-sans uppercase text-xs tracking-wide text-palette-600 transition-[filter] hover:brightness-90
|
||||||
${$primary ? "bg-primary" : "bg-white border-2 border-black"}`,
|
${$primary ? "bg-primary" : "bg-white border-2 border-black"}`,
|
||||||
}))``;
|
}))``;
|
||||||
Button.propTypes = {
|
Button.propTypes = {
|
||||||
|
|
|
@ -22,7 +22,7 @@ const TooltipContent = styled.div.attrs({
|
||||||
className: "bg-primary-light/10 text-palette-600 py-2 px-4 ",
|
className: "bg-primary-light/10 text-palette-600 py-2 px-4 ",
|
||||||
})``;
|
})``;
|
||||||
|
|
||||||
export const CopyButton = ({ value }) => {
|
export const CopyButton = ({ value, className }) => {
|
||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [timer, setTimer] = useState(null);
|
const [timer, setTimer] = useState(null);
|
||||||
|
@ -32,13 +32,13 @@ export const CopyButton = ({ value }) => {
|
||||||
copy(value);
|
copy(value);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
|
|
||||||
setTimer(setTimeout(() => setCopied(false), 150000));
|
setTimer(setTimeout(() => setCopied(false), 1500));
|
||||||
}, [value, timer]);
|
}, [value, timer]);
|
||||||
|
|
||||||
useClickAway(containerRef, () => setCopied(false));
|
useClickAway(containerRef, () => setCopied(false));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="inline-flex relative overflow-visible pr-2">
|
<div ref={containerRef} className={`inline-flex relative overflow-visible pr-2 ${className ?? ""}`}>
|
||||||
<Button onClick={handleCopy} className={copied ? "text-primary" : ""}>
|
<Button onClick={handleCopy} className={copied ? "text-primary" : ""}>
|
||||||
<CopyIcon size={16} />
|
<CopyIcon size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -30,7 +30,7 @@ const TriggerIcon = styled(ChevronDownIcon).attrs({
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Flyout = styled.div.attrs(({ open }) => ({
|
const Flyout = styled.div.attrs(({ open }) => ({
|
||||||
className: `absolute top-full right-0 p-0
|
className: `absolute top-full right-0 p-0 z-10
|
||||||
border rounded border-palette-100
|
border rounded border-palette-100
|
||||||
bg-white shadow-md shadow-palette-200/50
|
bg-white shadow-md shadow-palette-200/50
|
||||||
${open ? "visible" : "invisible"}`,
|
${open ? "visible" : "invisible"}`,
|
||||||
|
|
|
@ -3,18 +3,16 @@ import PropTypes from "prop-types";
|
||||||
|
|
||||||
const DropdownLink = styled.a.attrs({
|
const DropdownLink = styled.a.attrs({
|
||||||
className: `m-0 border-t border-palette-200/50 h-[60px]
|
className: `m-0 border-t border-palette-200/50 h-[60px]
|
||||||
whitespace-nowrap transition-colors text-current
|
whitespace-nowrap transition-colors
|
||||||
hover:bg-palette-100/50 flex items-center
|
hover:bg-palette-100/50 flex items-center
|
||||||
pr-8 pl-6 py-4 gap-4 first:border-0`,
|
pr-8 pl-6 py-4 gap-4 first:border-0`,
|
||||||
})``;
|
})``;
|
||||||
|
|
||||||
export const DropdownMenuLink = ({ active, icon: Icon, label, ...props }) => (
|
export const DropdownMenuLink = ({ active, icon: Icon, label, ...props }) => (
|
||||||
<>
|
|
||||||
<DropdownLink {...props}>
|
<DropdownLink {...props}>
|
||||||
{Icon ? <Icon className={active ? "text-primary" : "text-current"} /> : null}
|
{Icon ? <Icon /> : null}
|
||||||
{label}
|
<span className="text-palette-500">{label}</span>
|
||||||
</DropdownLink>
|
</DropdownLink>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
DropdownMenuLink.propTypes = {
|
DropdownMenuLink.propTypes = {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { withIconProps } from "../withIconProps";
|
||||||
|
|
||||||
|
export const SimpleUploadIcon = withIconProps(({ size, ...props }) => (
|
||||||
|
<svg width={size} height={size} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" {...props}>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19,12a1,1,0,0,1,1,.88V17a3,3,0,0,1-2.82,3H3a3,3,0,0,1-3-2.82V13a1,1,0,0,1,2-.12V17a1,1,0,0,0,.88,1H17a1,1,0,0,0,1-.88V13A1,1,0,0,1,19,12ZM10,0h0a1,1,0,0,1,.62.22l.09.07,5,5a1,1,0,0,1-1.32,1.5l-.1-.08L11,3.41V13a1,1,0,0,1-2,.12V3.41L5.71,6.71a1,1,0,0,1-1.32.08l-.1-.08a1,1,0,0,1-.08-1.32l.08-.1,5-5L9.38.22h0L9.29.29A1.05,1.05,0,0,1,10,0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
));
|
|
@ -12,3 +12,4 @@ export * from "./icons/PlusIcon";
|
||||||
export * from "./icons/SearchIcon";
|
export * from "./icons/SearchIcon";
|
||||||
export * from "./icons/CopyIcon";
|
export * from "./icons/CopyIcon";
|
||||||
export * from "./icons/ShareIcon";
|
export * from "./icons/ShareIcon";
|
||||||
|
export * from "./icons/SimpleUploadIcon";
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { PageContainer } from "../PageContainer";
|
||||||
import { NavBarLink, NavBarSection } from ".";
|
import { NavBarLink, NavBarSection } from ".";
|
||||||
|
|
||||||
const NavBarContainer = styled.div.attrs({
|
const NavBarContainer = styled.div.attrs({
|
||||||
className: `grid sticky top-0 bg-white`,
|
className: `grid sticky top-0 bg-white z-10 shadow-sm`,
|
||||||
})``;
|
})``;
|
||||||
|
|
||||||
const NavBarBody = styled.nav.attrs({
|
const NavBarBody = styled.nav.attrs({
|
||||||
|
@ -68,8 +68,21 @@ export const NavBar = () => (
|
||||||
</NavBarSection>
|
</NavBarSection>
|
||||||
<NavBarSection className="dropdown-area justify-end">
|
<NavBarSection className="dropdown-area justify-end">
|
||||||
<DropdownMenu title="My account">
|
<DropdownMenu title="My account">
|
||||||
<DropdownMenuLink href="/settings" icon={CogIcon} label="Settings" />
|
<DropdownMenuLink
|
||||||
<DropdownMenuLink href="/logout" icon={LockClosedIcon} label="Log out" />
|
to="/settings"
|
||||||
|
as={Link}
|
||||||
|
activeClassName="text-primary"
|
||||||
|
icon={CogIcon}
|
||||||
|
label="Settings"
|
||||||
|
partiallyActive
|
||||||
|
/>
|
||||||
|
<DropdownMenuLink
|
||||||
|
to="/logout"
|
||||||
|
as={Link}
|
||||||
|
activeClassName="text-primary"
|
||||||
|
icon={LockClosedIcon}
|
||||||
|
label="Log out"
|
||||||
|
/>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</NavBarSection>
|
</NavBarSection>
|
||||||
</NavBarBody>
|
</NavBarBody>
|
||||||
|
|
|
@ -21,7 +21,7 @@ const Label = styled.label.attrs({
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Toggle = styled.span.attrs({
|
const Toggle = styled.span.attrs({
|
||||||
className: `flex flex-row items-center justify-between
|
className: `flex flex-row items-center justify-between shrink-0
|
||||||
w-[44px] h-[22px] bg-white rounded-full
|
w-[44px] h-[22px] bg-white rounded-full
|
||||||
border border-palette-200 relative cursor-pointer`,
|
border border-palette-200 relative cursor-pointer`,
|
||||||
})`
|
})`
|
||||||
|
@ -45,7 +45,7 @@ const TogglePin = styled.span.attrs(({ $checked }) => ({
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Switch = ({ children, defaultChecked, onChange, ...props }) => {
|
export const Switch = ({ children, defaultChecked, labelClassName, onChange, ...props }) => {
|
||||||
const id = useMemo(nanoid, [onChange]);
|
const id = useMemo(nanoid, [onChange]);
|
||||||
const [checked, setChecked] = useState(defaultChecked);
|
const [checked, setChecked] = useState(defaultChecked);
|
||||||
|
|
||||||
|
@ -56,11 +56,11 @@ export const Switch = ({ children, defaultChecked, onChange, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<Container {...props}>
|
<Container {...props}>
|
||||||
<Checkbox checked={checked} onChange={(ev) => setChecked(ev.target.checked)} id={id} />
|
<Checkbox checked={checked} onChange={(ev) => setChecked(ev.target.checked)} id={id} />
|
||||||
<Label htmlFor={id}>
|
<Label htmlFor={id} className={labelClassName}>
|
||||||
<Toggle>
|
<Toggle>
|
||||||
<TogglePin $checked={checked} />
|
<TogglePin $checked={checked} />
|
||||||
</Toggle>
|
</Toggle>
|
||||||
{children}
|
<div className="-mt-0.5">{children}</div>
|
||||||
</Label>
|
</Label>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -74,7 +74,11 @@ Switch.propTypes = {
|
||||||
/**
|
/**
|
||||||
* Element to be rendered as the switch label
|
* Element to be rendered as the switch label
|
||||||
*/
|
*/
|
||||||
children: PropTypes.element,
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
||||||
|
/**
|
||||||
|
* Pass additional CSS classes to the `label` element.
|
||||||
|
*/
|
||||||
|
labelClassName: PropTypes.string,
|
||||||
/**
|
/**
|
||||||
* Function to execute on change
|
* Function to execute on change
|
||||||
*/
|
*/
|
||||||
|
@ -83,4 +87,5 @@ Switch.propTypes = {
|
||||||
|
|
||||||
Switch.defaultProps = {
|
Switch.defaultProps = {
|
||||||
defaultChecked: false,
|
defaultChecked: false,
|
||||||
|
labelClassName: "",
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export const TextInputBasic = ({ label, placeholder, ...props }) => {
|
||||||
|
const id = useMemo(() => `input-${nanoid()}`, []);
|
||||||
|
|
||||||
/**
|
|
||||||
* Primary UI component for user interaction
|
|
||||||
*/
|
|
||||||
export const TextInputBasic = ({ label, placeholder }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={""}>
|
<div className="flex flex-col w-full gap-1">
|
||||||
<p className={"font-sans uppercase text-palette-300 text-inputLabel mb-textInputLabelBottom"}>{label}</p>
|
<label className="font-sans uppercase text-palette-300 text-xs" htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id={id}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={
|
className="w-full py-2 px-4 bg-palette-100 rounded-sm placeholder:text-palette-200 focus:outline outline-1 outline-palette-200"
|
||||||
"w-full bg-palette-100 h-textInput px-textInputBasicX focus:outline-none bg-transparent " +
|
{...props}
|
||||||
"placeholder-palette-400 text-content tracking-inputPlaceholder text-textInput"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Link } from "gatsby";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { SWRConfig } from "swr";
|
||||||
|
|
||||||
|
import { authenticatedOnly } from "../lib/swrConfig";
|
||||||
|
|
||||||
|
import { PageContainer } from "../components/PageContainer";
|
||||||
|
import { NavBar } from "../components/Navbar";
|
||||||
|
import { Footer } from "../components/Footer";
|
||||||
|
import { UserProvider, useUser } from "../contexts/user";
|
||||||
|
import { ContainerLoadingIndicator } from "../components/LoadingIndicator";
|
||||||
|
|
||||||
|
const Wrapper = styled.div.attrs({
|
||||||
|
className: "min-h-screen overflow-hidden",
|
||||||
|
})`
|
||||||
|
background-image: url(/images/dashboard-bg.svg);
|
||||||
|
background-position: center -280px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Layout = ({ children }) => {
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
|
// Prevent from flashing the dashboard screen to unauthenticated users.
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
{!user && (
|
||||||
|
<div className="fixed inset-0 flex justify-center items-center bg-palette-100/50">
|
||||||
|
<ContainerLoadingIndicator className="!text-palette-200/50" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{user && <>{children}</>}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Sidebar = () => (
|
||||||
|
<aside className="w-full lg:w-48 bg-white text-sm font-sans font-light text-palette-600 shrink-0">
|
||||||
|
<nav>
|
||||||
|
<SidebarLink activeClassName="!border-l-primary" to="/settings">
|
||||||
|
Account
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink activeClassName="!border-l-primary" to="/settings/notifications">
|
||||||
|
Notifications
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink activeClassName="!border-l-primary" to="/settings/export">
|
||||||
|
Import / Export
|
||||||
|
</SidebarLink>
|
||||||
|
<SidebarLink activeClassName="!border-l-primary" to="/settings/api-keys">
|
||||||
|
API Keys
|
||||||
|
</SidebarLink>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SidebarLink = styled(Link).attrs({
|
||||||
|
className: `h-12 py-3 px-6 h-full w-full flex
|
||||||
|
border-l-2 border-l-palette-200
|
||||||
|
border-b border-b-palette-100 last:border-b-transparent`,
|
||||||
|
})``;
|
||||||
|
|
||||||
|
const Content = styled.main.attrs({
|
||||||
|
className: "relative bg-white rounded px-6 py-6 sm:px-16 sm:py-14 mt-6 lg:mt-0 bg-none xl:bg-corner-circle",
|
||||||
|
})`
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UserSettingsLayout = ({ children }) => (
|
||||||
|
<SWRConfig value={authenticatedOnly}>
|
||||||
|
<UserProvider>
|
||||||
|
<Layout>
|
||||||
|
<NavBar />
|
||||||
|
<PageContainer className="mt-2 md:mt-14">
|
||||||
|
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
|
||||||
|
<div className="flex flex-col lg:flex-row">
|
||||||
|
<Sidebar />
|
||||||
|
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
<Footer />
|
||||||
|
</Layout>
|
||||||
|
</UserProvider>
|
||||||
|
</SWRConfig>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UserSettingsLayout;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
|
||||||
|
const APIKeysPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>API Keys</h4>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
APIKeysPage.Layout = UserSettingsLayout;
|
||||||
|
|
||||||
|
export default APIKeysPage;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
|
||||||
|
const ExportPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>Import / export</h4>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ExportPage.Layout = UserSettingsLayout;
|
||||||
|
|
||||||
|
export default ExportPage;
|
|
@ -0,0 +1,81 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { useMedia } from "react-use";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import theme from "../../lib/theme";
|
||||||
|
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
import { TextInputBasic } from "../../components/TextInputBasic/TextInputBasic";
|
||||||
|
import { Button } from "../../components/Button";
|
||||||
|
import { AvatarUploader } from "../../components/AvatarUploader";
|
||||||
|
|
||||||
|
const FormGroup = styled.div.attrs({
|
||||||
|
className: "grid sm:grid-cols-[1fr_min-content] w-full gap-y-2 gap-x-4 items-end",
|
||||||
|
})``;
|
||||||
|
|
||||||
|
const AccountPage = () => {
|
||||||
|
const isLargeScreen = useMedia(`(min-width: ${theme.screens.xl})`);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col xl:flex-row">
|
||||||
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
|
<section>
|
||||||
|
<h4>Account</h4>
|
||||||
|
<p>
|
||||||
|
Tum dicere exorsus est laborum et quasi involuta aperiri, altera prompta et expedita. Primum igitur,
|
||||||
|
inquit, modo ista sis aequitate.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<hr />
|
||||||
|
{!isLargeScreen && (
|
||||||
|
<section>
|
||||||
|
<AvatarUploader className="flex flex-col sm:flex-row gap-8 items-center" />
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
<section className="flex flex-col gap-8">
|
||||||
|
<FormGroup>
|
||||||
|
<TextInputBasic label="Display name" placeholder="John Doe" />
|
||||||
|
<div className="flex mt-2 sm:mt-0 justify-center">
|
||||||
|
<Button>Update</Button>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<TextInputBasic label="Email" placeholder="john.doe@example.com" />
|
||||||
|
<div className="flex mt-2 sm:mt-0 justify-center">
|
||||||
|
<Button>Update</Button>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<TextInputBasic type="password" label="Password" placeholder="dbf3htf*efh4pcy@PXB" />
|
||||||
|
<div className="flex mt-2 sm:mt-0 justify-center order-last sm:order-none">
|
||||||
|
<Button>Update</Button>
|
||||||
|
</div>
|
||||||
|
<small className="text-palette-400">
|
||||||
|
The password must be at least 6 characters long. Significantly different from the email and old
|
||||||
|
password.
|
||||||
|
</small>
|
||||||
|
</FormGroup>
|
||||||
|
</section>
|
||||||
|
<hr />
|
||||||
|
<section>
|
||||||
|
<h6 className="text-palette-400">Delete account</h6>
|
||||||
|
<p>This will completely delete your account. This process can't be undone.</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => window.confirm("TODO: confirmation modal")}
|
||||||
|
className="text-error underline decoration-1 hover:decoration-dashed"
|
||||||
|
>
|
||||||
|
Delete account
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full justify-start xl:justify-end">
|
||||||
|
{isLargeScreen && <AvatarUploader className="flex flex-col gap-4" />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountPage.Layout = UserSettingsLayout;
|
||||||
|
|
||||||
|
export default AccountPage;
|
|
@ -0,0 +1,51 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
|
||||||
|
import { Switch } from "../../components/Switch";
|
||||||
|
import { StaticImage } from "gatsby-plugin-image";
|
||||||
|
|
||||||
|
const NotificationsPage = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
|
<h4>Notifications</h4>
|
||||||
|
<section>
|
||||||
|
{/* TODO: saves on change */}
|
||||||
|
<Switch onChange={console.info.bind(console)} labelClassName="!items-start flex-col md:flex-row">
|
||||||
|
I agreee to get the latest news, updates and special offers delivered to my email inbox.
|
||||||
|
</Switch>
|
||||||
|
</section>
|
||||||
|
<hr />
|
||||||
|
<section>
|
||||||
|
<h6 className="text-palette-300">Statistics</h6>
|
||||||
|
{/* TODO: proper content :) */}
|
||||||
|
<p>
|
||||||
|
Si sine causa, nollem me tamen laudandis maioribus meis corrupisti nec in malis. Si sine causa, mox
|
||||||
|
videro.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul className="mt-7 flex flex-col gap-2">
|
||||||
|
<li>
|
||||||
|
{/* TODO: saves on change */}
|
||||||
|
<Switch onChange={console.info.bind(console)}>Storage limit</Switch>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{/* TODO: saves on change */}
|
||||||
|
<Switch onChange={console.info.bind(console)}>File limit</Switch>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div className="hidden xl:block text-right w-full pr-14 pt-20">
|
||||||
|
<StaticImage src="../../../static/images/inbox.svg" alt="" placeholder="none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NotificationsPage.Layout = UserSettingsLayout;
|
||||||
|
|
||||||
|
export default NotificationsPage;
|
|
@ -39,7 +39,14 @@
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
h6 {
|
h6 {
|
||||||
@apply uppercase;
|
@apply uppercase text-xs;
|
||||||
font-size: 0.875rem;
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@apply mt-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
@apply border-t-palette-200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 160 160"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#f4f6f7;}.cls-3{fill:#231f20;}.cls-4{mask:url(#mask);}.cls-5,.cls-7{fill:none;stroke-width:2px;}.cls-5{stroke:#0f0f0f;stroke-linejoin:round;}.cls-6,.cls-8{fill:#40b75e;}.cls-6{fill-rule:evenodd;}.cls-7{stroke:#231f20;stroke-miterlimit:10;}</style><mask id="mask" x="15.52" y="37.16" width="128" height="146" maskUnits="userSpaceOnUse"><g id="gf1qkqaq7b"><circle id="asdc4b43va" class="cls-1" cx="79.52" cy="101.16" r="64"/></g></mask></defs><g id="Layer_2" data-name="Layer 2"><circle class="cls-2" cx="80" cy="80" r="79"/><path class="cls-3" d="M80,2A78,78,0,1,1,2,80,78.09,78.09,0,0,1,80,2m0-2a80,80,0,1,0,80,80A80,80,0,0,0,80,0Z"/></g><g id="Layer_1" data-name="Layer 1"><g class="cls-4"><path class="cls-5" d="M103.52,129.16l6,2c12,4,22,9.85,22,22v30m-40-70v10m-36,6-6,2c-12,4-22,9.85-22,22v30m40-70v10m-10-40,6-4h14l6-6,12,6,6,4v8l-2,2c0,10-6,24-20,24s-20-14-20-24l-2-2Z"/><path class="cls-5" d="M57.52,83.16l-2-2v-14l6-10,4,4,8-4,8,4h12a10,10,0,0,1,10,10v10l-2,2"/><path class="cls-6" d="M69.52,128.16l10,14,10-14h8l8,4-20,20h-12l-20-20,8-4Z"/><path class="cls-5" d="M69.52,123.16l10,14,10-14h8l8,4-20,20h-12l-20-20,8-4Z"/></g></g><g id="Layer_3" data-name="Layer 3"><circle class="cls-7" cx="95.3" cy="33.27" r="4.55"/><circle class="cls-8" cx="20.61" cy="86.7" r="2.29"/><circle class="cls-8" cx="128.65" cy="60.06" r="5.08"/></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 171 126"><defs><style>.cls-1{fill:#40b75e;fill-rule:evenodd;}.cls-2,.cls-3{fill:none;stroke:#0f0f0f;stroke-width:2px;}.cls-2{stroke-linejoin:round;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M107.89,26.18a23.09,23.09,0,0,1,9.26,1.94l-9.26,21.06,22,6.63a23,23,0,1,1-22-29.63Z"/><path class="cls-2" d="M115.89,45.18l-10,10-6-6"/><path class="cls-2" d="M123.89,49.18a16,16,0,1,1-16-16A16,16,0,0,1,123.89,49.18Z"/><path class="cls-3" d="M156,45h14v80H46V45H60"/><path class="cls-3" d="M150,65V17L134,1H66V65"/><path class="cls-2" d="M72,9h56M72,17h56M108,77,46,125H170ZM46,45,86,85m84-40L130,85"/><path class="cls-3" d="M150,17H134V1M106,117h4m4,0h4m-20,0h4"/><path class="cls-3" d="M29,45H0m34,9H15M44,34H25"/></g></g></svg>
|
After Width: | Height: | Size: 843 B |
|
@ -39,6 +39,10 @@ module.exports = {
|
||||||
tab: ["18px", "28px"],
|
tab: ["18px", "28px"],
|
||||||
},
|
},
|
||||||
backgroundColor: ["disabled"],
|
backgroundColor: ["disabled"],
|
||||||
|
backgroundImage: {
|
||||||
|
"corner-circle":
|
||||||
|
"radial-gradient(circle at calc(100% - 60px) -50px, #F5F5F7 0%, #f5f5f7 250px,rgba(0,0,0,0) 250px)",
|
||||||
|
},
|
||||||
textColor: ["disabled"],
|
textColor: ["disabled"],
|
||||||
keyframes: {
|
keyframes: {
|
||||||
wiggle: {
|
wiggle: {
|
||||||
|
@ -54,6 +58,8 @@ module.exports = {
|
||||||
"page-md": "640px",
|
"page-md": "640px",
|
||||||
"page-lg": "896px",
|
"page-lg": "896px",
|
||||||
"page-xl": "1312px",
|
"page-xl": "1312px",
|
||||||
|
"settings-lg": "704px",
|
||||||
|
"settings-xl": "928px",
|
||||||
},
|
},
|
||||||
minWidth: {
|
minWidth: {
|
||||||
button: "112px",
|
button: "112px",
|
||||||
|
|
Reference in New Issue