Dashboard v2 - fixes + publishing under /v2 path (#2008)
* fix(dashboard-v2): fix current usage graph on portals without Stripe configured * ops(dashboard-v2): publish new dashboard under /v2 prefix * ops(dashboard-v2): fix SkynetClient on multi-server portals * fix(dashboard-v2): fix skylinks validation for sponsor keys * ops(dashboard-v2): don't actually launch the new dashboard yet :) * chore(dashboard-v2): cleanup console.log call * fix(dashboard-v2): always set portal domain for SkynetClient
This commit is contained in:
parent
16c7e0bafa
commit
f433287bb6
|
@ -3,6 +3,11 @@ listen 443 ssl http2;
|
||||||
include /etc/nginx/conf.d/include/ssl-settings;
|
include /etc/nginx/conf.d/include/ssl-settings;
|
||||||
include /etc/nginx/conf.d/include/init-optional-variables;
|
include /etc/nginx/conf.d/include/init-optional-variables;
|
||||||
|
|
||||||
|
# Uncomment to launch new Dashboard under /v2 path
|
||||||
|
# location /v2 {
|
||||||
|
# proxy_pass http://dashboard-v2:9000;
|
||||||
|
# }
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://dashboard:3000;
|
proxy_pass http://dashboard:3000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ module.exports = {
|
||||||
title: `Account Dashboard`,
|
title: `Account Dashboard`,
|
||||||
siteUrl: `https://account.${GATSBY_PORTAL_DOMAIN}`,
|
siteUrl: `https://account.${GATSBY_PORTAL_DOMAIN}`,
|
||||||
},
|
},
|
||||||
|
pathPrefix: "/v2",
|
||||||
trailingSlash: "never",
|
trailingSlash: "never",
|
||||||
plugins: [
|
plugins: [
|
||||||
"gatsby-plugin-image",
|
"gatsby-plugin-image",
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
"develop": "gatsby develop",
|
"develop": "gatsby develop",
|
||||||
"develop:secure": "dotenv -e .env.development -- gatsby develop --https -p=443",
|
"develop:secure": "dotenv -e .env.development -- gatsby develop --https -p=443",
|
||||||
"start": "gatsby develop",
|
"start": "gatsby develop",
|
||||||
"build": "gatsby build",
|
"build": "gatsby build --prefix-paths",
|
||||||
"serve": "gatsby serve",
|
"serve": "gatsby serve --prefix-paths",
|
||||||
"clean": "gatsby clean",
|
"clean": "gatsby clean",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prettier": "prettier .",
|
"prettier": "prettier .",
|
||||||
|
|
|
@ -3,14 +3,14 @@ import { useEffect, useState } from "react";
|
||||||
import { useUser } from "../../contexts/user";
|
import { useUser } from "../../contexts/user";
|
||||||
// import { SimpleUploadIcon } from "../Icons";
|
// import { SimpleUploadIcon } from "../Icons";
|
||||||
|
|
||||||
const AVATAR_PLACEHOLDER = "/images/avatar-placeholder.svg";
|
import avatarPlaceholder from "../../../static/images/avatar-placeholder.svg";
|
||||||
|
|
||||||
export const AvatarUploader = (props) => {
|
export const AvatarUploader = (props) => {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const [imageUrl, setImageUrl] = useState(AVATAR_PLACEHOLDER);
|
const [imageUrl, setImageUrl] = useState(avatarPlaceholder);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setImageUrl(user.avatarUrl ?? AVATAR_PLACEHOLDER);
|
setImageUrl(user.avatarUrl ?? avatarPlaceholder);
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import fileSize from "pretty-bytes";
|
import fileSize from "pretty-bytes";
|
||||||
import { Link } from "gatsby";
|
import { Link } from "gatsby";
|
||||||
|
import cn from "classnames";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { useUser } from "../../contexts/user";
|
import { useUser } from "../../contexts/user";
|
||||||
|
@ -62,7 +63,9 @@ const ErrorMessage = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function CurrentUsage() {
|
export default function CurrentUsage() {
|
||||||
|
const { activePlan, plans } = useActivePlan();
|
||||||
const { usage, error, loading } = useUsageData();
|
const { usage, error, loading } = useUsageData();
|
||||||
|
const nextPlan = useMemo(() => plans.find(({ tier }) => tier > activePlan?.tier), [plans, activePlan]);
|
||||||
const storageUsage = size(usage.storageUsed);
|
const storageUsage = size(usage.storageUsed);
|
||||||
const storageLimit = size(usage.storageLimit);
|
const storageLimit = size(usage.storageLimit);
|
||||||
const filesUsedLabel = useMemo(() => ({ value: usage.filesUsed, unit: "files" }), [usage.filesUsed]);
|
const filesUsedLabel = useMemo(() => ({ value: usage.filesUsed, unit: "files" }), [usage.filesUsed]);
|
||||||
|
@ -89,7 +92,7 @@ export default function CurrentUsage() {
|
||||||
<span>{storageLimit.text}</span>
|
<span>{storageLimit.text}</span>
|
||||||
</div>
|
</div>
|
||||||
<UsageGraph>
|
<UsageGraph>
|
||||||
<GraphBar value={usage.storageUsed} limit={usage.storageLimit} label={storageUsage} />
|
<GraphBar value={usage.storageUsed} limit={usage.storageLimit} label={storageUsage} className="normal-case" />
|
||||||
<GraphBar value={usage.filesUsed} limit={usage.filesLimit} label={filesUsedLabel} />
|
<GraphBar value={usage.filesUsed} limit={usage.filesLimit} label={filesUsedLabel} />
|
||||||
</UsageGraph>
|
</UsageGraph>
|
||||||
<div className="flex place-content-between">
|
<div className="flex place-content-between">
|
||||||
|
@ -97,7 +100,10 @@ export default function CurrentUsage() {
|
||||||
<span className="inline-flex place-content-between w-[37%]">
|
<span className="inline-flex place-content-between w-[37%]">
|
||||||
<Link
|
<Link
|
||||||
to="/upgrade"
|
to="/upgrade"
|
||||||
className="text-primary underline-offset-3 decoration-dotted hover:text-primary-light hover:underline"
|
className={cn(
|
||||||
|
"text-primary underline-offset-3 decoration-dotted hover:text-primary-light hover:underline",
|
||||||
|
{ invisible: !nextPlan }
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
UPGRADE
|
UPGRADE
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
|
|
|
@ -21,11 +21,11 @@ const BarLabel = styled.span.attrs({
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GraphBar = ({ value, limit, label }) => {
|
export const GraphBar = ({ value, limit, label, className }) => {
|
||||||
const percentage = typeof limit !== "number" || limit === 0 ? 0 : (value / limit) * 100;
|
const percentage = typeof limit !== "number" || limit === 0 ? 0 : (value / limit) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center">
|
<div className={`relative flex items-center ${className}`}>
|
||||||
<Bar $percentage={percentage}>
|
<Bar $percentage={percentage}>
|
||||||
<BarTip />
|
<BarTip />
|
||||||
</Bar>
|
</Bar>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
import usageGraphBg from "../../../static/images/usage-graph-bg.svg";
|
||||||
|
|
||||||
export const UsageGraph = styled.div.attrs({
|
export const UsageGraph = styled.div.attrs({
|
||||||
className: "w-full my-3 grid grid-flow-row grid-rows-2",
|
className: "w-full my-3 grid grid-flow-row grid-rows-2",
|
||||||
})`
|
})`
|
||||||
height: 146px;
|
height: 146px;
|
||||||
background: url(/images/usage-graph-bg.svg) no-repeat;
|
background: url(${usageGraphBg}) no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -80,7 +80,7 @@ export const AddSponsorKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||||
json: {
|
json: {
|
||||||
name,
|
name,
|
||||||
public: "true",
|
public: "true",
|
||||||
skylinks: [...skylinks, nextSkylink].filter(Boolean).map(parseSkylink),
|
skylinks: [...skylinks, nextSkylink].filter(Boolean).map((skylink) => parseSkylink(skylink)),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
|
|
|
@ -19,7 +19,14 @@ const aggregatePlansAndLimits = (plans, limits, { includeFreePlan }) => {
|
||||||
|
|
||||||
// Decorate each plan with its corresponding limits data, if available.
|
// Decorate each plan with its corresponding limits data, if available.
|
||||||
if (limits?.length) {
|
if (limits?.length) {
|
||||||
return sortedPlans.map((plan) => ({ ...plan, limits: limits[plan.tier] || null }));
|
return limits.map((limitsDescriptor, index) => {
|
||||||
|
const asssociatedPlan = sortedPlans.find((plan) => plan.tier === index) || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...asssociatedPlan,
|
||||||
|
limits: limitsDescriptor || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have the limits data yet, set just return the plans.
|
// If we don't have the limits data yet, set just return the plans.
|
||||||
|
@ -40,10 +47,12 @@ export const PlansProvider = ({ children }) => {
|
||||||
if (plansError || limitsError) {
|
if (plansError || limitsError) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setError(plansError || limitsError);
|
setError(plansError || limitsError);
|
||||||
} else if (rawPlans) {
|
} else if (rawPlans || limits) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setPlans(
|
setPlans(
|
||||||
aggregatePlansAndLimits(rawPlans, limits?.userLimits, { includeFreePlan: !settings.isSubscriptionRequired })
|
aggregatePlansAndLimits(rawPlans || [], limits?.userLimits, {
|
||||||
|
includeFreePlan: !settings.isSubscriptionRequired,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [rawPlans, limits, plansError, limitsError, settings.isSubscriptionRequired]);
|
}, [rawPlans, limits, plansError, limitsError, settings.isSubscriptionRequired]);
|
||||||
|
|
|
@ -3,10 +3,13 @@ import styled from "styled-components";
|
||||||
|
|
||||||
import { UserProvider } from "../contexts/user";
|
import { UserProvider } from "../contexts/user";
|
||||||
|
|
||||||
|
import skynetLogo from "../../static/images/logo-black-text.svg";
|
||||||
|
import authBg from "../../static/images/auth-bg.svg";
|
||||||
|
|
||||||
const Layout = styled.div.attrs({
|
const Layout = styled.div.attrs({
|
||||||
className: "min-h-screen w-screen bg-black flex",
|
className: "min-h-screen w-screen bg-black flex",
|
||||||
})`
|
})`
|
||||||
background-image: url(/images/auth-bg.svg);
|
background-image: url(${authBg});
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
`;
|
`;
|
||||||
|
@ -36,7 +39,7 @@ const AuthLayout =
|
||||||
<Content>
|
<Content>
|
||||||
<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" className="-ml-2" />
|
<img src={skynetLogo} alt="Skynet" className="-ml-2" />
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,10 +7,12 @@ import { Footer } from "../components/Footer";
|
||||||
import { UserProvider, useUser } from "../contexts/user";
|
import { UserProvider, useUser } from "../contexts/user";
|
||||||
import { FullScreenLoadingIndicator } from "../components/LoadingIndicator";
|
import { FullScreenLoadingIndicator } from "../components/LoadingIndicator";
|
||||||
|
|
||||||
|
import dashboardBg from "../../static/images/dashboard-bg.svg";
|
||||||
|
|
||||||
const Wrapper = styled.div.attrs({
|
const Wrapper = styled.div.attrs({
|
||||||
className: "min-h-screen overflow-hidden",
|
className: "min-h-screen overflow-hidden",
|
||||||
})`
|
})`
|
||||||
background-image: url(/images/dashboard-bg.svg);
|
background-image: url(${dashboardBg});
|
||||||
background-position: center -280px;
|
background-position: center -280px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { AddSponsorKeyForm } from "../../components/forms/AddSponsorKeyForm";
|
||||||
import { Metadata } from "../../components/Metadata";
|
import { Metadata } from "../../components/Metadata";
|
||||||
import HighlightedLink from "../../components/HighlightedLink";
|
import HighlightedLink from "../../components/HighlightedLink";
|
||||||
|
|
||||||
|
import apiKeysImg from "../../../static/images/api-keys.svg";
|
||||||
|
|
||||||
const DeveloperSettingsPage = () => {
|
const DeveloperSettingsPage = () => {
|
||||||
const { data: allKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
const { data: allKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
||||||
const apiKeys = allKeys.filter(({ public: isPublic }) => isPublic === "false");
|
const apiKeys = allKeys.filter(({ public: isPublic }) => isPublic === "false");
|
||||||
|
@ -103,7 +105,7 @@ const DeveloperSettingsPage = () => {
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden xl:block w-full text-right pt-16 pr-5">
|
<div className="hidden xl:block w-full text-right pt-16 pr-5">
|
||||||
<img src="/images/api-keys.svg" alt="" className="inline-block h-[150px]" />
|
<img src={apiKeysImg} alt="" className="inline-block h-[150px]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { Switch } from "../../components/Switch";
|
||||||
import { Button } from "../../components/Button";
|
import { Button } from "../../components/Button";
|
||||||
import { Metadata } from "../../components/Metadata";
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
|
import exportImg from "../../../static/images/import-export.svg";
|
||||||
|
|
||||||
const useExportOptions = () => {
|
const useExportOptions = () => {
|
||||||
const [pinnedFiles, setPinnedFiles] = useState(false);
|
const [pinnedFiles, setPinnedFiles] = useState(false);
|
||||||
const [uploadHistory, setUploadHistory] = useState(false);
|
const [uploadHistory, setUploadHistory] = useState(false);
|
||||||
|
@ -65,7 +67,7 @@ const ExportPage = () => {
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden xl:block w-full text-right pt-20 pr-6">
|
<div className="hidden xl:block w-full text-right pt-20 pr-6">
|
||||||
<img src="/images/import-export.svg" alt="" className="inline-block w-[200px]" />
|
<img src={exportImg} alt="" className="inline-block w-[200px]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
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 { Metadata } from "../../components/Metadata";
|
import { Metadata } from "../../components/Metadata";
|
||||||
|
|
||||||
|
import inboxImg from "../../../static/images/inbox.svg";
|
||||||
|
|
||||||
const NotificationsPage = () => {
|
const NotificationsPage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -37,8 +38,8 @@ const NotificationsPage = () => {
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden xl:block text-right w-full pr-14 pt-20">
|
<div className="hidden xl:block text-right w-full pl-12 pt-20">
|
||||||
<StaticImage src="../../../static/images/inbox.svg" alt="" placeholder="none" />
|
<img src={inboxImg} alt="" className="w-[200px]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
Reference in New Issue