Dashboard v2 lighthouse scores improvements (#1972)
* Metadata improvements * Accessibility improvements * Improve performance on mobile
This commit is contained in:
parent
d077a27fa9
commit
7bd54359e6
|
@ -2,8 +2,8 @@ const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
title: `Accounts Dashboard`,
|
title: "Skynet Account",
|
||||||
siteUrl: `https://www.yourdomain.tld`,
|
siteUrl: `https://account.${process.env.GATSBY_PORTAL_DOMAIN}/`,
|
||||||
},
|
},
|
||||||
trailingSlash: "never",
|
trailingSlash: "never",
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
@ -83,13 +83,19 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
{isPublic && (
|
{isPublic && (
|
||||||
<button
|
<button
|
||||||
title="Add or remove skylinks"
|
title="Add or remove skylinks"
|
||||||
|
aria-label="Add or remove skylinks"
|
||||||
className="p-1 transition-colors hover:text-primary"
|
className="p-1 transition-colors hover:text-primary"
|
||||||
onClick={promptEdit}
|
onClick={promptEdit}
|
||||||
>
|
>
|
||||||
<CogIcon size={22} />
|
<CogIcon size={22} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button title="Delete this API key" className="p-1 transition-colors hover:text-error" onClick={promptRemoval}>
|
<button
|
||||||
|
title="Delete this API key"
|
||||||
|
aria-label="Delete this API key"
|
||||||
|
className="p-1 transition-colors hover:text-error"
|
||||||
|
onClick={promptRemoval}
|
||||||
|
>
|
||||||
<TrashIcon size={16} />
|
<TrashIcon size={16} />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -121,7 +127,11 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||||
<code className="whitespace-nowrap select-all truncate bg-palette-100 odd:bg-white p-1">
|
<code className="whitespace-nowrap select-all truncate bg-palette-100 odd:bg-white p-1">
|
||||||
{skylink}
|
{skylink}
|
||||||
</code>
|
</code>
|
||||||
<button className="p-1 transition-colors hover:text-error" onClick={() => removeSkylink(skylink)}>
|
<button
|
||||||
|
className="p-1 transition-colors hover:text-error"
|
||||||
|
onClick={() => removeSkylink(skylink)}
|
||||||
|
aria-label="Remove skylink"
|
||||||
|
>
|
||||||
<TrashIcon size={16} />
|
<TrashIcon size={16} />
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -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, className }) => {
|
export const CopyButton = ({ value, className, ariaLabel = "Copy" }) => {
|
||||||
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);
|
||||||
|
@ -39,7 +39,7 @@ export const CopyButton = ({ value, className }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className={`inline-flex relative overflow-visible pr-2 ${className ?? ""}`}>
|
<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" : ""} aria-label={ariaLabel}>
|
||||||
<CopyIcon size={16} />
|
<CopyIcon size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
<TooltipContainer $visible={copied}>
|
<TooltipContainer $visible={copied}>
|
||||||
|
|
|
@ -101,7 +101,6 @@ export default function CurrentUsage() {
|
||||||
>
|
>
|
||||||
UPGRADE
|
UPGRADE
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
{/* TODO: proper URL */}
|
|
||||||
<span>{usage.filesLimit}</span>
|
<span>{usage.filesLimit}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -84,19 +84,19 @@ export default function FileTable({ items }) {
|
||||||
<TableCell className="w-[180px]">{date}</TableCell>
|
<TableCell className="w-[180px]">{date}</TableCell>
|
||||||
<TableCell className="hidden lg:table-cell pr-6 !overflow-visible">
|
<TableCell className="hidden lg:table-cell pr-6 !overflow-visible">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<CopyButton value={skylink} className="mr-2" />
|
<CopyButton value={skylink} className="mr-2" aria-label="Copy skylink" />
|
||||||
<span className="w-full inline-block truncate">{skylink}</span>
|
<span className="w-full inline-block truncate">{skylink}</span>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="w-[100px] !overflow-visible">
|
<TableCell className="w-[100px] !overflow-visible">
|
||||||
<div className="flex text-palette-600 gap-4">
|
<div className="flex text-palette-600 gap-4">
|
||||||
<PopoverMenu options={buildShareMenu(item)} openClassName="text-primary">
|
<PopoverMenu options={buildShareMenu(item)} openClassName="text-primary">
|
||||||
<button>
|
<button aria-label="Share this skylink">
|
||||||
<ShareIcon size={22} />
|
<ShareIcon size={22} />
|
||||||
</button>
|
</button>
|
||||||
</PopoverMenu>
|
</PopoverMenu>
|
||||||
<PopoverMenu options={buildOptionsMenu(item)} openClassName="text-primary">
|
<PopoverMenu options={buildOptionsMenu(item)} openClassName="text-primary">
|
||||||
<button>
|
<button aria-label="Manage this skylink">
|
||||||
<CogIcon />
|
<CogIcon />
|
||||||
</button>
|
</button>
|
||||||
</PopoverMenu>
|
</PopoverMenu>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { graphql, useStaticQuery } from "gatsby";
|
||||||
|
|
||||||
|
export const Metadata = ({ children }) => {
|
||||||
|
const { site } = useStaticQuery(
|
||||||
|
graphql`
|
||||||
|
query Q {
|
||||||
|
site {
|
||||||
|
siteMetadata {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const { title } = site.siteMetadata;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet htmlAttributes={{ lang: "en" }} titleTemplate={`%s | ${title}`} defaultTitle={title}>
|
||||||
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
<meta name="description" content="Manage your Skynet uploads, account subscription, settings and API keys" />
|
||||||
|
<link rel="preconnect" href={`https://${process.env.GATSBY_PORTAL_DOMAIN}/`} />
|
||||||
|
{children}
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./Metadata";
|
|
@ -11,7 +11,7 @@ export const Modal = ({ children, className, onClose }) => (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
<Overlay onClick={onClose}>
|
<Overlay onClick={onClose}>
|
||||||
<div className={cn("relative w-modal max-w-modal shadow-sm rounded")}>
|
<div className={cn("relative w-modal max-w-modal shadow-sm rounded")}>
|
||||||
<button onClick={onClose} className="absolute top-[20px] right-[20px]">
|
<button onClick={onClose} className="absolute top-[20px] right-[20px]" aria-label="Close">
|
||||||
<PlusIcon size={14} className="rotate-45" />
|
<PlusIcon size={14} className="rotate-45" />
|
||||||
</button>
|
</button>
|
||||||
<Panel className={cn("px-8 py-6 sm:px-12 sm:py-10", className)}>{children}</Panel>
|
<Panel className={cn("px-8 py-6 sm:px-12 sm:py-10", className)}>{children}</Panel>
|
||||||
|
|
|
@ -94,6 +94,7 @@ export const NavBar = () => {
|
||||||
partiallyActive
|
partiallyActive
|
||||||
/>
|
/>
|
||||||
<DropdownMenuLink
|
<DropdownMenuLink
|
||||||
|
as="button"
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
activeClassName="text-primary"
|
activeClassName="text-primary"
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
|
|
@ -12,6 +12,7 @@ export default function Bullets({ visibleSlides, activeIndex, allSlides, changeS
|
||||||
.map((_, index) => (
|
.map((_, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
|
aria-label={`Slide ${index + 1}`}
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded-full w-3 h-3 ${activeIndex === index ? "bg-primary" : "border-2 cursor-pointer"}`}
|
className={`rounded-full w-3 h-3 ${activeIndex === index ? "bg-primary" : "border-2 cursor-pointer"}`}
|
||||||
onClick={(event) => changeSlide(event, index)}
|
onClick={(event) => changeSlide(event, index)}
|
||||||
|
|
|
@ -105,7 +105,11 @@ const Uploader = ({ mode }) => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<Button $primary className="w-[40px] h-[40px] !p-0 inline-flex justify-center items-center">
|
<Button
|
||||||
|
$primary
|
||||||
|
className="w-[40px] h-[40px] !p-0 inline-flex justify-center items-center"
|
||||||
|
aria-label="Upload new file"
|
||||||
|
>
|
||||||
<PlusIcon size={12} />
|
<PlusIcon size={12} />
|
||||||
</Button>
|
</Button>
|
||||||
<span className="ml-4">Add, or drop your files here</span>
|
<span className="ml-4">Add, or drop your files here</span>
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
||||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||||
{generatedKey}
|
{generatedKey}
|
||||||
</code>
|
</code>
|
||||||
<CopyButton value={generatedKey} className="whitespace-nowrap" />
|
<CopyButton value={generatedKey} className="whitespace-nowrap" aria-label="Copy the new API key" />
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
@ -94,7 +94,7 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
||||||
) : (
|
) : (
|
||||||
<Button type="submit" className="px-2.5">
|
<Button type="submit" className="px-2.5" aria-label="Create general API key">
|
||||||
<PlusIcon size={14} />
|
<PlusIcon size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -59,7 +59,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||||
{generatedKey}
|
{generatedKey}
|
||||||
</code>
|
</code>
|
||||||
<CopyButton value={generatedKey} className="whitespace-nowrap" />
|
<CopyButton value={generatedKey} className="whitespace-nowrap" aria-label="Copy the new public API key" />
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
@ -137,7 +137,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
touched={skylinksTouched[index]}
|
touched={skylinksTouched[index]}
|
||||||
/>
|
/>
|
||||||
<span className="w-[24px] shrink-0 mt-3">
|
<span className="w-[24px] shrink-0 mt-3">
|
||||||
<button type="button" onClick={() => remove(index)}>
|
<button type="button" onClick={() => remove(index)} aria-label="Remove this skylink">
|
||||||
<TrashIcon size={16} />
|
<TrashIcon size={16} />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -160,6 +160,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label="Add this skylink"
|
||||||
onClick={() => appendSkylink(values.nextSkylink)}
|
onClick={() => appendSkylink(values.nextSkylink)}
|
||||||
className={cn("shrink-0 mt-1.5 w-[24px] h-[24px]", {
|
className={cn("shrink-0 mt-1.5 w-[24px] h-[24px]", {
|
||||||
"text-palette-300 cursor-not-allowed": isNextSkylinkInvalid,
|
"text-palette-300 cursor-not-allowed": isNextSkylinkInvalid,
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const AddSkylinkToAPIKeyForm = ({ addSkylink }) => (
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
||||||
) : (
|
) : (
|
||||||
<Button type="submit" className="px-2.5">
|
<Button type="submit" className="px-2.5" aria-label="Add this skylink">
|
||||||
<PlusIcon size={14} />
|
<PlusIcon size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { navigate } from "gatsby";
|
||||||
import AuthLayout from "../../layouts/AuthLayout";
|
import AuthLayout from "../../layouts/AuthLayout";
|
||||||
import { LoginForm } from "../../components/forms";
|
import { LoginForm } from "../../components/forms";
|
||||||
import { useUser } from "../../contexts/user";
|
import { useUser } from "../../contexts/user";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const LoginPage = ({ location }) => {
|
const LoginPage = ({ location }) => {
|
||||||
const { user, mutate: refreshUserState } = useUser();
|
const { user, mutate: refreshUserState } = useUser();
|
||||||
|
@ -17,16 +18,21 @@ const LoginPage = ({ location }) => {
|
||||||
}, [user, redirectTo]);
|
}, [user, redirectTo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<>
|
||||||
<div className="mb-4 md:mb-16">
|
<Metadata>
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
<title>Sign In</title>
|
||||||
|
</Metadata>
|
||||||
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
|
<div className="mb-4 md:mb-16">
|
||||||
|
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||||
|
</div>
|
||||||
|
<LoginForm
|
||||||
|
onSuccess={async () => {
|
||||||
|
await refreshUserState();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<LoginForm
|
</>
|
||||||
onSuccess={async () => {
|
|
||||||
await refreshUserState();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import AuthLayout from "../../layouts/AuthLayout";
|
||||||
|
|
||||||
import { RecoveryForm } from "../../components/forms/RecoveryForm";
|
import { RecoveryForm } from "../../components/forms/RecoveryForm";
|
||||||
import HighlightedLink from "../../components/HighlightedLink";
|
import HighlightedLink from "../../components/HighlightedLink";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
Pure: "PURE",
|
Pure: "PURE",
|
||||||
|
@ -15,31 +16,36 @@ const ResetPasswordPage = () => {
|
||||||
const [state, setState] = useState(State.Pure);
|
const [state, setState] = useState(State.Pure);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<>
|
||||||
<div className="mb-4 md:mb-16">
|
<Metadata>
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
<title>Reset Password</title>
|
||||||
|
</Metadata>
|
||||||
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
|
<div className="mb-4 md:mb-16">
|
||||||
|
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||||
|
</div>
|
||||||
|
{state !== State.Success && (
|
||||||
|
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === State.Success && (
|
||||||
|
<p className="text-primary text-center font-semibold">Please check your inbox for further instructions.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === State.Failure && (
|
||||||
|
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-sm text-center mt-8">
|
||||||
|
<p>
|
||||||
|
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Don't actually have an account? <HighlightedLink to="/auth/signup">Create one!</HighlightedLink>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{state !== State.Success && (
|
</>
|
||||||
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{state === State.Success && (
|
|
||||||
<p className="text-primary text-center font-semibold">Please check your inbox for further instructions.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{state === State.Failure && (
|
|
||||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="text-sm text-center mt-8">
|
|
||||||
<p>
|
|
||||||
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Don't actually have an account? <HighlightedLink to="/auth/signup">Create one!</HighlightedLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import HighlightedLink from "../../components/HighlightedLink";
|
||||||
import { SignUpForm } from "../../components/forms/SignUpForm";
|
import { SignUpForm } from "../../components/forms/SignUpForm";
|
||||||
import { usePortalSettings } from "../../contexts/portal-settings";
|
import { usePortalSettings } from "../../contexts/portal-settings";
|
||||||
import { PlansProvider, usePlans } from "../../contexts/plans";
|
import { PlansProvider, usePlans } from "../../contexts/plans";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const FreePortalHeader = () => {
|
const FreePortalHeader = () => {
|
||||||
const { plans } = usePlans();
|
const { plans } = usePlans();
|
||||||
|
@ -57,6 +58,9 @@ const SignUpPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlansProvider>
|
<PlansProvider>
|
||||||
|
<Metadata>
|
||||||
|
<title>Sign Up</title>
|
||||||
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
<div className="mb-4 md:mb-16">
|
<div className="mb-4 md:mb-16">
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||||
|
|
|
@ -1,28 +1,34 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useSearchParam } from "react-use";
|
||||||
|
|
||||||
import DashboardLayout from "../layouts/DashboardLayout";
|
import DashboardLayout from "../layouts/DashboardLayout";
|
||||||
|
|
||||||
import { Panel } from "../components/Panel";
|
import { Panel } from "../components/Panel";
|
||||||
import { Tab, TabPanel, Tabs } from "../components/Tabs";
|
import { Tab, TabPanel, Tabs } from "../components/Tabs";
|
||||||
|
import { Metadata } from "../components/Metadata";
|
||||||
import FileList from "../components/FileList/FileList";
|
import FileList from "../components/FileList/FileList";
|
||||||
import { useSearchParam } from "react-use";
|
|
||||||
|
|
||||||
const FilesPage = () => {
|
const FilesPage = () => {
|
||||||
const defaultTab = useSearchParam("tab");
|
const defaultTab = useSearchParam("tab");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel title="Files">
|
<>
|
||||||
<Tabs defaultTab={defaultTab || "uploads"}>
|
<Metadata>
|
||||||
<Tab id="uploads" title="Uploads" />
|
<title>My Files</title>
|
||||||
<Tab id="downloads" title="Downloads" />
|
</Metadata>
|
||||||
<TabPanel tabId="uploads" className="pt-4">
|
<Panel title="Files">
|
||||||
<FileList type="uploads" />
|
<Tabs defaultTab={defaultTab || "uploads"}>
|
||||||
</TabPanel>
|
<Tab id="uploads" title="Uploads" />
|
||||||
<TabPanel tabId="downloads" className="pt-4">
|
<Tab id="downloads" title="Downloads" />
|
||||||
<FileList type="downloads" />
|
<TabPanel tabId="uploads" className="pt-4">
|
||||||
</TabPanel>
|
<FileList type="uploads" />
|
||||||
</Tabs>
|
</TabPanel>
|
||||||
</Panel>
|
<TabPanel tabId="downloads" className="pt-4">
|
||||||
|
<FileList type="downloads" />
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import CurrentUsage from "../components/CurrentUsage";
|
||||||
import Uploader from "../components/Uploader/Uploader";
|
import Uploader from "../components/Uploader/Uploader";
|
||||||
import CurrentPlan from "../components/CurrentPlan";
|
import CurrentPlan from "../components/CurrentPlan";
|
||||||
import { FullScreenLoadingIndicator } from "../components/LoadingIndicator";
|
import { FullScreenLoadingIndicator } from "../components/LoadingIndicator";
|
||||||
|
import { Metadata } from "../components/Metadata";
|
||||||
import useUpgradeRedirect from "../hooks/useUpgradeRedirect";
|
import useUpgradeRedirect from "../hooks/useUpgradeRedirect";
|
||||||
|
|
||||||
const IndexPage = () => {
|
const IndexPage = () => {
|
||||||
|
@ -24,51 +25,60 @@ const IndexPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlansProvider>
|
<>
|
||||||
<div className="w-full">
|
<Metadata>
|
||||||
<Slider
|
<title>Dashboard</title>
|
||||||
slides={[
|
</Metadata>
|
||||||
<Panel title="Upload" className="h-[330px]">
|
{verifyingSubscription ? (
|
||||||
<Tabs variant="fill">
|
<FullScreenLoadingIndicator />
|
||||||
<Tab id="files" title="Files" />
|
) : (
|
||||||
<Tab id="directory" title="Directory" />
|
<PlansProvider>
|
||||||
<TabPanel tabId="files" className="h-full overflow-y-auto">
|
<div className="w-full">
|
||||||
<Uploader mode="file" />
|
<Slider
|
||||||
</TabPanel>
|
slides={[
|
||||||
<TabPanel tabId="directory" className="h-full overflow-y-auto">
|
<Panel title="Upload" className="h-[330px]">
|
||||||
<Uploader mode="directory" />
|
<Tabs variant="fill">
|
||||||
</TabPanel>
|
<Tab id="files" title="Files" />
|
||||||
</Tabs>
|
<Tab id="directory" title="Directory" />
|
||||||
</Panel>,
|
<TabPanel tabId="files" className="h-full overflow-y-auto">
|
||||||
<Panel
|
<Uploader mode="file" />
|
||||||
title={
|
</TabPanel>
|
||||||
<>
|
<TabPanel tabId="directory" className="h-full overflow-y-auto">
|
||||||
<ArrowRightIcon /> Usage
|
<Uploader mode="directory" />
|
||||||
</>
|
</TabPanel>
|
||||||
}
|
</Tabs>
|
||||||
className="h-[330px]"
|
</Panel>,
|
||||||
>
|
<Panel
|
||||||
<CurrentUsage />
|
title={
|
||||||
</Panel>,
|
<>
|
||||||
<Panel
|
<ArrowRightIcon /> Usage
|
||||||
title={
|
</>
|
||||||
<>
|
}
|
||||||
<ArrowRightIcon /> Current plan
|
className="h-[330px]"
|
||||||
</>
|
>
|
||||||
}
|
<CurrentUsage />
|
||||||
className="h-[330px]"
|
</Panel>,
|
||||||
>
|
<Panel
|
||||||
<CurrentPlan />
|
title={
|
||||||
</Panel>,
|
<>
|
||||||
]}
|
<ArrowRightIcon /> Current plan
|
||||||
/>
|
</>
|
||||||
</div>
|
}
|
||||||
{showRecentActivity && (
|
className="h-[330px]"
|
||||||
<div className="mt-10">
|
>
|
||||||
<LatestActivity />
|
<CurrentPlan />
|
||||||
</div>
|
</Panel>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showRecentActivity && (
|
||||||
|
<div className="mt-10">
|
||||||
|
<LatestActivity />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</PlansProvider>
|
||||||
)}
|
)}
|
||||||
</PlansProvider>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { AddAPIKeyForm, APIKeyType } from "../../components/forms/AddAPIKeyForm"
|
||||||
import { APIKeyList } from "../../components/APIKeyList/APIKeyList";
|
import { APIKeyList } from "../../components/APIKeyList/APIKeyList";
|
||||||
import { Alert } from "../../components/Alert";
|
import { Alert } from "../../components/Alert";
|
||||||
import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm";
|
import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const APIKeysPage = () => {
|
const APIKeysPage = () => {
|
||||||
const { data: apiKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
const { data: apiKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
||||||
|
@ -29,6 +30,9 @@ const APIKeysPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Metadata>
|
||||||
|
<title>API Keys</title>
|
||||||
|
</Metadata>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<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]">
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
|
||||||
import { Switch } from "../../components/Switch";
|
import { Switch } from "../../components/Switch";
|
||||||
import { Button } from "../../components/Button";
|
import { Button } from "../../components/Button";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const useExportOptions = () => {
|
const useExportOptions = () => {
|
||||||
const [pinnedFiles, setPinnedFiles] = useState(false);
|
const [pinnedFiles, setPinnedFiles] = useState(false);
|
||||||
|
@ -29,6 +30,9 @@ const ExportPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Metadata>
|
||||||
|
<title>Export</title>
|
||||||
|
</Metadata>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<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]">
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { AccountSettingsForm } from "../../components/forms/AccountSettingsForm"
|
||||||
import { Modal } from "../../components/Modal/Modal";
|
import { Modal } from "../../components/Modal/Modal";
|
||||||
import { AccountRemovalForm } from "../../components/forms/AccountRemovalForm";
|
import { AccountRemovalForm } from "../../components/forms/AccountRemovalForm";
|
||||||
import { Alert } from "../../components/Alert";
|
import { Alert } from "../../components/Alert";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
Pure: "PURE",
|
Pure: "PURE",
|
||||||
|
@ -39,6 +40,9 @@ const AccountPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Metadata>
|
||||||
|
<title>Settings</title>
|
||||||
|
</Metadata>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<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]">
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { StaticImage } from "gatsby-plugin-image";
|
||||||
|
|
||||||
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
||||||
|
|
||||||
import { Switch } from "../../components/Switch";
|
import { Switch } from "../../components/Switch";
|
||||||
import { StaticImage } from "gatsby-plugin-image";
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const NotificationsPage = () => {
|
const NotificationsPage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Metadata>
|
||||||
|
<title>Notifications</title>
|
||||||
|
</Metadata>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||||
<h4>Notifications</h4>
|
<h4>Notifications</h4>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { Button } from "../components/Button";
|
||||||
import { usePortalSettings } from "../contexts/portal-settings";
|
import { usePortalSettings } from "../contexts/portal-settings";
|
||||||
import { Alert } from "../components/Alert";
|
import { Alert } from "../components/Alert";
|
||||||
import HighlightedLink from "../components/HighlightedLink";
|
import HighlightedLink from "../components/HighlightedLink";
|
||||||
|
import { Metadata } from "../components/Metadata";
|
||||||
|
|
||||||
const PAID_PORTAL_BREAKPOINTS = [
|
const PAID_PORTAL_BREAKPOINTS = [
|
||||||
{
|
{
|
||||||
|
@ -88,6 +89,9 @@ const PlansSlider = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mb-24">
|
<div className="w-full mb-24">
|
||||||
|
<Metadata>
|
||||||
|
<title>Upgrade</title>
|
||||||
|
</Metadata>
|
||||||
{settings.isSubscriptionRequired && !activePlan && (
|
{settings.isSubscriptionRequired && !activePlan && (
|
||||||
<Alert $variant="info" className="mb-6">
|
<Alert $variant="info" className="mb-6">
|
||||||
<p className="font-semibold mt-0">This Skynet portal requires a paid subscription.</p>
|
<p className="font-semibold mt-0">This Skynet portal requires a paid subscription.</p>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AllUsersAuthLayout } from "../../layouts/AuthLayout";
|
||||||
|
|
||||||
import HighlightedLink from "../../components/HighlightedLink";
|
import HighlightedLink from "../../components/HighlightedLink";
|
||||||
import accountsService from "../../services/accountsService";
|
import accountsService from "../../services/accountsService";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
Pure: "PURE",
|
Pure: "PURE",
|
||||||
|
@ -52,24 +53,29 @@ const EmailConfirmationPage = ({ location }) => {
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<>
|
||||||
<div className="mb-4 md:mb-16">
|
<Metadata>
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
<title>Confirm E-mail Address</title>
|
||||||
</div>
|
</Metadata>
|
||||||
<div className="text-center">
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
<div className="mb-4 md:mb-16">
|
||||||
|
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
||||||
|
|
||||||
{state === State.Success && (
|
{state === State.Success && (
|
||||||
<>
|
<>
|
||||||
<p className="text-primary font-semibold">All done!</p>
|
<p className="text-primary font-semibold">All done!</p>
|
||||||
<p>You will be redirected to your dashboard shortly.</p>
|
<p>You will be redirected to your dashboard shortly.</p>
|
||||||
<HighlightedLink to="/">Redirect now.</HighlightedLink>
|
<HighlightedLink to="/">Redirect now.</HighlightedLink>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import AuthLayout from "../../layouts/AuthLayout";
|
||||||
|
|
||||||
import { ResetPasswordForm } from "../../components/forms/ResetPasswordForm";
|
import { ResetPasswordForm } from "../../components/forms/ResetPasswordForm";
|
||||||
import HighlightedLink from "../../components/HighlightedLink";
|
import HighlightedLink from "../../components/HighlightedLink";
|
||||||
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
Pure: "PURE",
|
Pure: "PURE",
|
||||||
|
@ -19,35 +20,40 @@ const RecoverPage = ({ location }) => {
|
||||||
const [state, setState] = useState(State.Pure);
|
const [state, setState] = useState(State.Pure);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<>
|
||||||
<div className="mb-4 md:mb-16">
|
<Metadata>
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
<title>Recover Your Account</title>
|
||||||
</div>
|
</Metadata>
|
||||||
{state !== State.Success && (
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
<ResetPasswordForm
|
<div className="mb-4 md:mb-16">
|
||||||
token={token}
|
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||||
onSuccess={() => {
|
</div>
|
||||||
setState(State.Success);
|
{state !== State.Success && (
|
||||||
navigate("/");
|
<ResetPasswordForm
|
||||||
}}
|
token={token}
|
||||||
onFailure={() => setState(State.Failure)}
|
onSuccess={() => {
|
||||||
/>
|
setState(State.Success);
|
||||||
)}
|
navigate("/");
|
||||||
|
}}
|
||||||
|
onFailure={() => setState(State.Failure)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{state === State.Success && (
|
{state === State.Success && (
|
||||||
<p className="text-primary text-center font-semibold">
|
<p className="text-primary text-center font-semibold">
|
||||||
All done! You will be redirected to your dashboard shortly.
|
All done! You will be redirected to your dashboard shortly.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === State.Failure && (
|
||||||
|
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-sm text-center mt-8">
|
||||||
|
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||||
</p>
|
</p>
|
||||||
)}
|
</div>
|
||||||
|
</>
|
||||||
{state === State.Failure && (
|
|
||||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p className="text-sm text-center mt-8">
|
|
||||||
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Reference in New Issue