diff --git a/packages/dashboard-v2/.storybook/main.js b/packages/dashboard-v2/.storybook/main.js index 09e2ce48..8a1198a4 100644 --- a/packages/dashboard-v2/.storybook/main.js +++ b/packages/dashboard-v2/.storybook/main.js @@ -3,6 +3,7 @@ module.exports = { addons: [ "@storybook/addon-links", "@storybook/addon-essentials", + "storybook-addon-gatsby", { name: "@storybook/addon-postcss", options: { diff --git a/packages/dashboard-v2/gatsby-config.js b/packages/dashboard-v2/gatsby-config.js index b82a34b1..b742e91b 100644 --- a/packages/dashboard-v2/gatsby-config.js +++ b/packages/dashboard-v2/gatsby-config.js @@ -9,6 +9,7 @@ module.exports = { "gatsby-plugin-react-helmet", "gatsby-plugin-sharp", "gatsby-transformer-sharp", + "gatsby-plugin-styled-components", "gatsby-plugin-postcss", { resolve: "gatsby-source-filesystem", diff --git a/packages/dashboard-v2/gatsby-ssr.js b/packages/dashboard-v2/gatsby-ssr.js new file mode 100644 index 00000000..a71e49c3 --- /dev/null +++ b/packages/dashboard-v2/gatsby-ssr.js @@ -0,0 +1,13 @@ +import * as React from "react"; +import "@fontsource/sora/300.css"; // light +import "@fontsource/sora/400.css"; // normal +import "@fontsource/sora/500.css"; // medium +import "@fontsource/sora/600.css"; // semibold +import "@fontsource/source-sans-pro/400.css"; // normal +import "@fontsource/source-sans-pro/600.css"; // semibold +import "./src/styles/global.css"; + +export function wrapPageElement({ element, props }) { + const Layout = element.type.Layout ?? React.Fragment; + return {element}; +} diff --git a/packages/dashboard-v2/package.json b/packages/dashboard-v2/package.json index c5bb6214..8d088e7c 100644 --- a/packages/dashboard-v2/package.json +++ b/packages/dashboard-v2/package.json @@ -24,6 +24,7 @@ "gatsby": "^4.6.2", "gatsby-plugin-postcss": "^5.7.0", "postcss": "^8.4.6", + "pretty-bytes": "^6.0.0", "react": "^17.0.1", "react-dom": "^17.0.1", "react-helmet": "^6.1.0", @@ -44,20 +45,23 @@ "autoprefixer": "^10.4.2", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.3", + "babel-plugin-preval": "^5.1.0", "babel-plugin-styled-components": "^2.0.2", "eslint": "^8.9.0", "eslint-config-react-app": "^7.0.0", "eslint-plugin-storybook": "^0.5.6", "gatsby-plugin-alias-imports": "^1.0.5", "gatsby-plugin-image": "^2.6.0", + "gatsby-plugin-preval": "^1.0.0", "gatsby-plugin-provide-react": "^1.0.2", "gatsby-plugin-react-helmet": "^5.6.0", "gatsby-plugin-sharp": "^4.6.0", - "gatsby-plugin-styled-components": "^5.7.0", + "gatsby-plugin-styled-components": "^5.8.0", "gatsby-source-filesystem": "^4.6.0", "gatsby-transformer-sharp": "^4.6.0", "prettier": "2.5.1", "react-is": "^17.0.2", + "storybook-addon-gatsby": "^0.0.5", "styled-components": "^5.3.3" } } diff --git a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js new file mode 100644 index 00000000..b467e1ea --- /dev/null +++ b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js @@ -0,0 +1,71 @@ +import * as React from "react"; +import fileSize from "pretty-bytes"; +import { Link } from "gatsby"; + +import { GraphBar } from "./GraphBar"; +import { UsageGraph } from "./UsageGraph"; + +// TODO: get real data +const useUsageData = () => ({ + files: { + used: 19_521, + limit: 20_000, + }, + storage: { + used: 23_000_000_000, + limit: 1_000_000_000_000, + }, +}); + +const size = (bytes) => { + const text = fileSize(bytes, { maximumFractionDigits: 1 }); + const [value, unit] = text.split(" "); + + return { + text, + value, + unit, + }; +}; + +export default function CurrentUsage() { + const { files, storage } = useUsageData(); + + const storageUsage = size(storage.used); + const storageLimit = size(storage.limit); + const filesUsedLabel = React.useMemo(() => ({ value: files.used, unit: "files" }), [files.used]); + + return ( + <> +

+ {storageUsage.text} of {storageLimit.text} +

+

+ {files.used} of {files.limit} files +

+
+
+ Storage + {storageLimit.text} +
+ + + + +
+ Files + + + UPGRADE + {" "} + {/* TODO: proper URL */} + {files.limit} + +
+
+ + ); +} diff --git a/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js b/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js new file mode 100644 index 00000000..96421f6e --- /dev/null +++ b/packages/dashboard-v2/src/components/CurrentUsage/GraphBar.js @@ -0,0 +1,35 @@ +import styled from "styled-components"; + +const Bar = styled.div.attrs({ + className: `relative flex justify-end h-4 bg-primary rounded-l rounded-r-lg`, +})` + min-width: 1rem; + width: ${({ $percentage }) => $percentage}%; +`; + +const BarTip = styled.span.attrs({ + className: "relative w-4 h-4 border-2 rounded-full bg-white border-primary", +})``; + +const BarLabel = styled.span.attrs({ + className: "bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow", +})` + right: max(0%, ${({ $percentage }) => 100 - $percentage}%); + top: -0.5rem; + transform: translateX(50%); +`; + +export const GraphBar = ({ value, limit, label }) => { + const percentage = typeof limit !== "number" || limit === 0 ? 0 : (value / limit) * 100; + + return ( +
+ + + + + {label.value} {label.unit} + +
+ ); +}; diff --git a/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js b/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js new file mode 100644 index 00000000..3f6f23c2 --- /dev/null +++ b/packages/dashboard-v2/src/components/CurrentUsage/UsageGraph.js @@ -0,0 +1,9 @@ +import styled from "styled-components"; + +export const UsageGraph = styled.div.attrs({ + className: "w-full my-3 grid grid-flow-row grid-rows-2", +})` + height: 146px; + background: url(/images/usage-graph-bg.svg) no-repeat; + background-size: cover; +`; diff --git a/packages/dashboard-v2/src/components/CurrentUsage/index.js b/packages/dashboard-v2/src/components/CurrentUsage/index.js new file mode 100644 index 00000000..802aa4e1 --- /dev/null +++ b/packages/dashboard-v2/src/components/CurrentUsage/index.js @@ -0,0 +1,3 @@ +import CurrentUsage from "./CurrentUsage"; + +export default CurrentUsage; diff --git a/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js b/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js new file mode 100644 index 00000000..095ad728 --- /dev/null +++ b/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js @@ -0,0 +1,22 @@ +import * as React from "react"; +import { Table, TableBody, TableCell, TableRow } from "../Table"; + +export default function ActivityTable({ data }) { + return ( + + + {data.map(({ name, type, size, uploaded, skylink }) => ( + + {name} + {type} + + {size} + + {uploaded} + {skylink} + + ))} + +
+ ); +} diff --git a/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js b/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js new file mode 100644 index 00000000..7e7fdf6c --- /dev/null +++ b/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js @@ -0,0 +1,24 @@ +import * as React from "react"; +import { Panel } from "../Panel"; +import { Tab, TabPanel, Tabs } from "../Tabs"; +import ActivityTable from "./ActivityTable"; +import useRecentActivityData from "./useActivityData"; + +export default function LatestActivity() { + const { downloads, uploads } = useRecentActivityData(); + + return ( + + + + + + + + + + + + + ); +} diff --git a/packages/dashboard-v2/src/components/LatestActivity/index.js b/packages/dashboard-v2/src/components/LatestActivity/index.js new file mode 100644 index 00000000..32a02f82 --- /dev/null +++ b/packages/dashboard-v2/src/components/LatestActivity/index.js @@ -0,0 +1,3 @@ +import LatestActivity from "./LatestActivity"; + +export default LatestActivity; diff --git a/packages/dashboard-v2/src/components/LatestActivity/useActivityData.js b/packages/dashboard-v2/src/components/LatestActivity/useActivityData.js new file mode 100644 index 00000000..a262816e --- /dev/null +++ b/packages/dashboard-v2/src/components/LatestActivity/useActivityData.js @@ -0,0 +1,54 @@ +const UPLOADS_DATA = [ + { + name: "At_vereo_eos_censes", + type: ".mp4", + size: "2.45 MB", + uploaded: "a few seconds ago", + skylink: "_HyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1izriu", + }, + { + name: "Miriam Klein IV", + type: ".pdf", + size: "7.52 MB", + uploaded: "01/04/2021; 17:11", + skylink: "_izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5P1", + }, + { + name: "tmp/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm/QmWR6eVDVkwhAYq7X99w4xT9KNKBzwK39Fj1PDmr4ZnzMm", + type: ".doc", + size: "8.15 MB", + uploaded: "10/26/2020; 7:21", + skylink: "_VXeRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtB", + }, + { + name: "Perm_London", + type: ".avi", + size: "225.6 MB", + uploaded: "09/12/2020; 19:28", + skylink: "_eRDgaDAAWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVX", + }, + { + name: "Santa_Clara", + type: ".pdf", + size: "7.52 MB", + uploaded: "09/12/2020; 19:23", + skylink: "_AWg6Bmm5P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDA", + }, + { + name: "Marysa_Labrone", + type: ".doc", + size: "8.15 MB", + uploaded: "09/12/2020; 19:21", + skylink: "_P1izriuHyFqH632Rmy99c93idTtBVXeRDgaDAAWg6Bmm5", + }, +]; + +const DOWNLOADS_DATA = UPLOADS_DATA.slice().reverse(); + +// TODO: get real data +export default function useRecentActivityData() { + return { + uploads: UPLOADS_DATA, + downloads: DOWNLOADS_DATA, + }; +} diff --git a/packages/dashboard-v2/src/components/NavBar/NavBar.js b/packages/dashboard-v2/src/components/NavBar/NavBar.js index 257b8a0d..2b53f2be 100644 --- a/packages/dashboard-v2/src/components/NavBar/NavBar.js +++ b/packages/dashboard-v2/src/components/NavBar/NavBar.js @@ -1,21 +1,78 @@ +import { Link } from "gatsby"; import styled from "styled-components"; +import { screen } from "../../lib/cssHelpers"; +import { DropdownMenu, DropdownMenuLink } from "../DropdownMenu"; +import { CogIcon, LockClosedIcon, SkynetLogoIcon } from "../Icons"; import { PageContainer } from "../PageContainer"; +import { NavBarLink, NavBarSection } from "."; + const NavBarContainer = styled.div.attrs({ className: `grid sticky top-0 bg-white`, })``; const NavBarBody = styled.nav.attrs({ - className: "grid h-[80px] font-sans font-light text-sm", + className: "grid font-sans font-light text-xs sm:text-sm", })` - grid-template-columns: auto max-content 1fr; + height: 100px; + grid-template-columns: 1fr 1fr; + grid-template-rows: 60px 40px; + grid-template-areas: + "logo dropdown" + "navigation navigation"; + + ${screen( + "sm", + ` + height: 80px; + grid-template-columns: auto max-content 1fr; + grid-template-areas: "logo navigation dropdown"; + grid-template-rows: auto; + ` + )} + + .navigation-area { + grid-area: navigation; + display: grid; + grid-template-columns: repeat(3, 1fr); + } + + .logo-area { + grid-area: logo; + justify-content: start; + } + + .dropdown-area { + grid-area: dropdown; + } `; -export const NavBar = (props) => ( +export const NavBar = () => ( - - + + + + + + + + Dashboard + + + Files + + + Payments + + + + + + + + + ); diff --git a/packages/dashboard-v2/src/components/NavBar/NavBar.stories.js b/packages/dashboard-v2/src/components/NavBar/NavBar.stories.js index e1022faa..0dc7c925 100644 --- a/packages/dashboard-v2/src/components/NavBar/NavBar.stories.js +++ b/packages/dashboard-v2/src/components/NavBar/NavBar.stories.js @@ -9,17 +9,7 @@ export default { }, }; -const Template = (props) => ( - - - - Dashboard - - Files - Payments - - -); +const Template = (props) => ; export const DashboardTopNavigation = Template.bind({}); DashboardTopNavigation.args = {}; diff --git a/packages/dashboard-v2/src/components/NavBar/NavBarLink.js b/packages/dashboard-v2/src/components/NavBar/NavBarLink.js index 772933c0..3c85dcda 100644 --- a/packages/dashboard-v2/src/components/NavBar/NavBarLink.js +++ b/packages/dashboard-v2/src/components/NavBar/NavBarLink.js @@ -3,7 +3,7 @@ import styled from "styled-components"; export const NavBarLink = styled.a.attrs(({ active }) => ({ className: ` - min-w-[168px] + sm:min-w-[133px] lg:min-w-[168px] flex h-full items-center justify-center border-x border-x-palette-100 border-b-2 text-palette-600 transition-colors hover:bg-palette-100/50 diff --git a/packages/dashboard-v2/src/components/PageContainer/PageContainer.js b/packages/dashboard-v2/src/components/PageContainer/PageContainer.js index 9c95b59c..2fa037a8 100644 --- a/packages/dashboard-v2/src/components/PageContainer/PageContainer.js +++ b/packages/dashboard-v2/src/components/PageContainer/PageContainer.js @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import styled from "styled-components"; export const PageContainer = styled.div.attrs({ - className: `mx-auto w-page md:w-page-md lg:w-page-lg xl:w-page-xl`, + className: `mx-auto w-page lg:w-page-lg xl:w-page-xl px-2 md:px-16 lg:px-0`, })``; PageContainer.propTypes = { diff --git a/packages/dashboard-v2/src/components/Panel/Panel.js b/packages/dashboard-v2/src/components/Panel/Panel.js index ea79b4d1..27551ecd 100644 --- a/packages/dashboard-v2/src/components/Panel/Panel.js +++ b/packages/dashboard-v2/src/components/Panel/Panel.js @@ -25,7 +25,7 @@ Panel.propTypes = { /** * Label of the panel */ - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), }; Panel.defaultProps = { diff --git a/packages/dashboard-v2/src/components/Slider/Bullets.js b/packages/dashboard-v2/src/components/Slider/Bullets.js new file mode 100644 index 00000000..b43c66b5 --- /dev/null +++ b/packages/dashboard-v2/src/components/Slider/Bullets.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; + +export default function Bullets({ visibleSlides, activeIndex, allSlides, changeSlide }) { + if (allSlides <= visibleSlides) { + return null; + } + + return ( +
+ {Array(allSlides - visibleSlides + 1) + .fill(null) + .map((_, index) => ( +
+ ); +} + +Bullets.propTypes = { + allSlides: PropTypes.number.isRequired, + activeIndex: PropTypes.number.isRequired, + visibleSlides: PropTypes.number.isRequired, + changeSlide: PropTypes.func.isRequired, +}; diff --git a/packages/dashboard-v2/src/components/Slider/Slide.js b/packages/dashboard-v2/src/components/Slider/Slide.js new file mode 100644 index 00000000..4f700502 --- /dev/null +++ b/packages/dashboard-v2/src/components/Slider/Slide.js @@ -0,0 +1,12 @@ +import styled from "styled-components"; +import PropTypes from "prop-types"; + +const Slide = styled.div.attrs(({ isVisible }) => ({ + className: `slider-slide transition-opacity ${isVisible ? "" : "opacity-50 cursor-pointer"}`, +}))``; + +Slide.propTypes = { + isVisible: PropTypes.bool.isRequired, +}; + +export default Slide; diff --git a/packages/dashboard-v2/src/components/Slider/Slider.js b/packages/dashboard-v2/src/components/Slider/Slider.js new file mode 100644 index 00000000..1a04485d --- /dev/null +++ b/packages/dashboard-v2/src/components/Slider/Slider.js @@ -0,0 +1,127 @@ +import * as React from "react"; +import PropTypes from "prop-types"; +import styled, { css } from "styled-components"; +import theme from "../../lib/theme"; + +import useActiveBreakpoint from "./useActiveBreakpoint"; +import Bullets from "./Bullets"; +import Slide from "./Slide"; + +const Container = styled.div.attrs({ + className: "slider w-full", +})``; + +/** + * Styles applied to the movable element when the number of slide elements + * exceeds the number of visible slides for the current breakpoint + * */ +const scrollableStyles = css` + ${({ $allSlides, $visibleSlides, $activeIndex }) => ` + transform: translateX(calc(-1 * ${$activeIndex} * ((100% + 1rem) / ${$visibleSlides}))); + grid-template-columns: repeat(${$allSlides}, calc((100% - ${$visibleSlides - 1}rem) / ${$visibleSlides})); + `} +`; + +const Scroller = styled.div.attrs({ + className: "slider-scroller grid gap-4 transition-transform", +})` + ${({ $scrollable }) => ($scrollable ? scrollableStyles : "")} +`; + +const Slider = ({ slides, breakpoints }) => { + const { visibleSlides, scrollable } = useActiveBreakpoint(breakpoints); + const [activeIndex, setActiveIndex] = React.useState(0); + const changeSlide = React.useCallback( + (index) => { + setActiveIndex(Math.min(index, slides.length - visibleSlides)); // Don't let it scroll too far + }, + [slides, visibleSlides, setActiveIndex] + ); + + React.useEffect(() => { + const maxIndex = slides.length - visibleSlides; + + // Make sure to not scroll too far when screen size changes. + if (activeIndex > maxIndex) { + setActiveIndex(maxIndex); + } + }, [slides.length, visibleSlides, activeIndex]); + + return ( + + + {slides.map((slide, index) => { + const isVisible = index >= activeIndex && index < activeIndex + visibleSlides; + + return ( +
+ changeSlide(index) : null} + > + {slide} + +
+ ); + })} +
+ {scrollable && ( + + )} +
+ ); +}; + +Slider.propTypes = { + slides: PropTypes.arrayOf(PropTypes.node.isRequired), + breakpoints: PropTypes.arrayOf( + PropTypes.shape({ + /** + * Breakpoint name as defined in Tailwind config. If not defined, config + * will be applied to all non-configured screen sizes. + */ + name: PropTypes.string, + /** + * Number of slides visible for a given breakpoint. + */ + visibleSlides: PropTypes.number.isRequired, + /** + * Whether or not the list should be scrollable horizontally at the given breakpoint. + * If set to false, all slides will be visible & rendered in a column. + */ + scrollable: PropTypes.bool.isRequired, + }) + ), +}; + +Slider.defaultProps = { + breakpoints: [ + { + name: "xl", + scrollable: true, + visibleSlides: 3, + }, + { + name: "md", + scrollable: true, + visibleSlides: 2, + }, + { + // For the smallest screens, we won't scroll but instead stack the slides vertically. + scrollable: false, + visibleSlides: 1, + }, + ], +}; + +export default Slider; diff --git a/packages/dashboard-v2/src/components/Slider/index.js b/packages/dashboard-v2/src/components/Slider/index.js new file mode 100644 index 00000000..a5890919 --- /dev/null +++ b/packages/dashboard-v2/src/components/Slider/index.js @@ -0,0 +1 @@ +export * from "./Slider"; diff --git a/packages/dashboard-v2/src/components/Slider/useActiveBreakpoint.js b/packages/dashboard-v2/src/components/Slider/useActiveBreakpoint.js new file mode 100644 index 00000000..4b891ba7 --- /dev/null +++ b/packages/dashboard-v2/src/components/Slider/useActiveBreakpoint.js @@ -0,0 +1,38 @@ +import { useEffect, useMemo, useCallback, useState } from "react"; +import { useWindowSize } from "react-use"; + +import theme from "../../lib/theme"; + +const { screens } = theme; + +export default function useActiveBreakpoint(breakpoints) { + const { width: windowWidth } = useWindowSize(); + const monitoredBreakpoints = useMemo( + () => + breakpoints + .slice() + // Map breakpoint names to their min-width configured in Tailwind + .map(({ name, ...config }) => { + // If breakpoint name is not configured, + // we'll apply this config to all unmatched breakpoints. + const minWidth = screens[name] ? parseInt(screens[name], 10) : -Infinity; + + return { minWidth, ...config }; + }) + // Since our breakpoints are setup with min-width rule, we need to sort them from largest to smallest + .sort(({ minWidth: widthA }, { minWidth: widthB }) => widthB - widthA), + [breakpoints] + ); + const findActiveBreakpoint = useCallback( + () => monitoredBreakpoints.find((breakpoint) => windowWidth >= breakpoint.minWidth), + [monitoredBreakpoints, windowWidth] + ); + + const [activeBreakpoint, setActiveBreakpoint] = useState(findActiveBreakpoint()); + + useEffect(() => { + setActiveBreakpoint(findActiveBreakpoint()); + }, [findActiveBreakpoint]); + + return activeBreakpoint; +} diff --git a/packages/dashboard-v2/src/components/Table/TableCell.js b/packages/dashboard-v2/src/components/Table/TableCell.js index 98f2bd3d..12a45a72 100644 --- a/packages/dashboard-v2/src/components/Table/TableCell.js +++ b/packages/dashboard-v2/src/components/Table/TableCell.js @@ -4,7 +4,7 @@ import styled from "styled-components"; * Accepts all HMTL attributes a `` element does. */ export const TableCell = styled.td.attrs({ - className: `px-6 py-4 h-tableRow truncate + className: `first:pl-6 last:pr-6 px-2 py-4 h-tableRow truncate text-palette-600 even:text-palette-400 first:rounded-l-sm last:rounded-r-sm`, })` diff --git a/packages/dashboard-v2/src/components/Tabs/ActiveTabIndicator.js b/packages/dashboard-v2/src/components/Tabs/ActiveTabIndicator.js index f6f89266..e39a5f08 100644 --- a/packages/dashboard-v2/src/components/Tabs/ActiveTabIndicator.js +++ b/packages/dashboard-v2/src/components/Tabs/ActiveTabIndicator.js @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; +import { useWindowSize } from "react-use"; const Wrapper = styled.div.attrs({ className: "absolute left-0 bottom-0 w-full h-0.5 bg-palette-200", @@ -15,6 +16,7 @@ const Indicator = styled.div.attrs({ export const ActiveTabIndicator = ({ tabRef }) => { const [position, setPosition] = useState(0); const [width, setWidth] = useState(0); + const { width: windowWidth } = useWindowSize(); useEffect(() => { if (!tabRef?.current) { @@ -24,7 +26,7 @@ export const ActiveTabIndicator = ({ tabRef }) => { const { offsetLeft, offsetWidth } = tabRef.current; setPosition(offsetLeft); setWidth(offsetWidth); - }, [tabRef]); + }, [tabRef, windowWidth]); return ( @@ -33,6 +35,9 @@ export const ActiveTabIndicator = ({ tabRef }) => { ); }; -ActiveTabIndicator.propTypes = { - tabRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.instanceOf(Element) })]), -}; +// Needed, because we're using an Element constant here which Gatsby doesn't recognize during build time. +if (typeof window !== "undefined") { + ActiveTabIndicator.propTypes = { + tabRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.instanceOf(Element) })]), + }; +} diff --git a/packages/dashboard-v2/src/layouts/DashboardLayout.js b/packages/dashboard-v2/src/layouts/DashboardLayout.js index 61d57870..bfd23c42 100644 --- a/packages/dashboard-v2/src/layouts/DashboardLayout.js +++ b/packages/dashboard-v2/src/layouts/DashboardLayout.js @@ -1,50 +1,23 @@ import * as React from "react"; -import { Link } from "gatsby"; - import styled from "styled-components"; -import { DropdownMenu, DropdownMenuLink } from "../components/DropdownMenu"; + import { PageContainer } from "../components/PageContainer"; -import { CogIcon, SkynetLogoIcon, LockClosedIcon } from "../components/Icons"; -import { NavBar, NavBarLink, NavBarSection } from "../components/Navbar"; +import { NavBar } from "../components/Navbar"; import { Footer } from "../components/Footer"; const Layout = styled.div.attrs({ - className: "h-screen overflow-hidden", + className: "min-h-screen overflow-hidden", })` background-image: url(/images/dashboard-bg.svg); - background-position: -300px -280px; - - .navbar { - grid-template-columns: auto max-content 1fr; - } + background-position: center -280px; + background-repeat: no-repeat; `; const DashboardLayout = ({ children }) => { return ( <> - - - - - - - Dashboard - - - Files - - - Payments - - - - - - - - - +
{children}
diff --git a/packages/dashboard-v2/src/lib/cssHelpers.js b/packages/dashboard-v2/src/lib/cssHelpers.js new file mode 100644 index 00000000..b47aac00 --- /dev/null +++ b/packages/dashboard-v2/src/lib/cssHelpers.js @@ -0,0 +1,21 @@ +import { css } from "styled-components"; +import theme from "./theme"; + +export const screen = (breakpoint, style) => { + const { screens } = theme; + const minWidth = screens[breakpoint]; + + if (typeof minWidth === "undefined") { + throw ReferenceError( + `Screen "${breakpoint}" is not defined in Tailwind config. Available values are: ${Object.keys(screens).join( + ", " + )}.` + ); + } + + return css` + @media (min-width: ${minWidth}) { + ${style} + } + `; +}; diff --git a/packages/dashboard-v2/src/lib/theme.js b/packages/dashboard-v2/src/lib/theme.js new file mode 100644 index 00000000..1df7f9f5 --- /dev/null +++ b/packages/dashboard-v2/src/lib/theme.js @@ -0,0 +1,8 @@ +// @preval +// This file is pre-evaluated on build-time, so the config is only resolved once +// and then included in the bundle for us to use in dynamic styles. +import resolveConfig from "tailwindcss/resolveConfig"; + +import tailwindConfig from "../../tailwind.config.js"; + +export default resolveConfig(tailwindConfig).theme; diff --git a/packages/dashboard-v2/src/pages/index.js b/packages/dashboard-v2/src/pages/index.js index 9bf765cb..a8d48ae2 100644 --- a/packages/dashboard-v2/src/pages/index.js +++ b/packages/dashboard-v2/src/pages/index.js @@ -1,9 +1,67 @@ import * as React from "react"; +import { useMedia } from "react-use"; +import theme from "../lib/theme"; +import { ArrowRightIcon } from "../components/Icons"; +import { Panel } from "../components/Panel"; +import { Tab, TabPanel, Tabs } from "../components/Tabs"; +import LatestActivity from "../components/LatestActivity/LatestActivity"; import DashboardLayout from "../layouts/DashboardLayout"; +import Slider from "../components/Slider/Slider"; +import CurrentUsage from "../components/CurrentUsage"; const IndexPage = () => { - return <>Dashboard; + const showRecentActivity = useMedia(`(min-width: ${theme.screens.md})`); + + return ( + <> +
+ + + + + +
Upload files...
+
+ +
Upload a directory...
+
+
+ , + + Usage + + } + > + + , + + Current plan + + } + > +
    +
  • Current
  • +
  • Plan
  • +
  • Info
  • +
+
, + ]} + /> +
+ {showRecentActivity && ( +
+ +
+ )} + + ); }; IndexPage.Layout = DashboardLayout; diff --git a/packages/dashboard-v2/static/images/usage-graph-bg.svg b/packages/dashboard-v2/static/images/usage-graph-bg.svg new file mode 100644 index 00000000..be055905 --- /dev/null +++ b/packages/dashboard-v2/static/images/usage-graph-bg.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/dashboard-v2/yarn.lock b/packages/dashboard-v2/yarn.lock index 8ec21531..03554f2e 100644 --- a/packages/dashboard-v2/yarn.lock +++ b/packages/dashboard-v2/yarn.lock @@ -338,7 +338,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.15.5", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.15.5", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.7.0": version "7.17.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0" integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA== @@ -1139,7 +1139,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.15.4", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.15.4", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.17.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== @@ -3156,6 +3156,39 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== +"@types/babel__core@^7.1.12": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8" + integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -4679,6 +4712,16 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" +babel-plugin-preval@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-preval/-/babel-plugin-preval-5.1.0.tgz#6efb89bf6b97af592cd1400c6df49c0e9e6ab027" + integrity sha512-G5R+xmo5LS41A4UyZjOjV0mp9AvkuCyUOAJ6TOv/jTZS+VKh7L7HUDRcCSOb0YCM/u0fFarh7Diz0wjY8rFNFg== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/babel__core" "^7.1.12" + babel-plugin-macros "^3.0.1" + require-from-string "^2.0.2" + babel-plugin-react-docgen@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz#7cc8e2f94e8dc057a06e953162f0810e4e72257b" @@ -8159,6 +8202,11 @@ gatsby-plugin-postcss@^5.7.0: "@babel/runtime" "^7.15.4" postcss-loader "^4.3.0" +gatsby-plugin-preval@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gatsby-plugin-preval/-/gatsby-plugin-preval-1.0.0.tgz#b0e9dcc9ef568cb6ca998f7211b5365824b97201" + integrity sha512-HpPp2bdA4nZsuD3R++GRhM9BPlFp8+ilkXIo53hNr14OlHXxrgimh9lqVVvP3q4JriHh+bYcvmfqm6msIsxxLg== + gatsby-plugin-provide-react@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/gatsby-plugin-provide-react/-/gatsby-plugin-provide-react-1.0.2.tgz#e50bb311cd8ef5855c6d94f708266ab117c77a15" @@ -8196,10 +8244,10 @@ gatsby-plugin-sharp@^4.6.0: svgo "1.3.2" uuid "3.4.0" -gatsby-plugin-styled-components@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/gatsby-plugin-styled-components/-/gatsby-plugin-styled-components-5.7.0.tgz#8ba7b4ddb1722dcd0efd4fc6f1a8e62f47be012c" - integrity sha512-mX8N4nqIX0Ow/pUSORUb8WlKvgX7foCoWZ0AifyBOFnhBCbRWYTsXFWwiea6jCnST5V61b2TOFpjIHcvHvc9aQ== +gatsby-plugin-styled-components@^5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/gatsby-plugin-styled-components/-/gatsby-plugin-styled-components-5.8.0.tgz#5d8c81802ed9266435aa1145451bfb3ac582ad37" + integrity sha512-4ma9PgOr3U5TUX6uwAqFW+VX+fDxmt1y4oM3ArfZufaiQvZJ52cuf/uiyI+Tx1DJebcypEpR5dXYVl9ZX1bUHg== dependencies: "@babel/runtime" "^7.15.4" @@ -12402,6 +12450,11 @@ pretty-bytes@^5.4.1: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== +pretty-bytes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140" + integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg== + pretty-error@^2.1.1, pretty-error@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" @@ -14061,6 +14114,11 @@ store2@^2.12.0: resolved "https://registry.yarnpkg.com/store2/-/store2-2.13.1.tgz#fae7b5bb9d35fc53dc61cd262df3abb2f6e59022" integrity sha512-iJtHSGmNgAUx0b/MCS6ASGxb//hGrHHRgzvN+K5bvkBTN7A9RTpPSf1WSp+nPGvWCJ1jRnvY7MKnuqfoi3OEqg== +storybook-addon-gatsby@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/storybook-addon-gatsby/-/storybook-addon-gatsby-0.0.5.tgz#94f5b67bab8659d0248b65e60dabc3702818ce8b" + integrity sha512-18f8Kc6mx8mEFfqY2DgF9ayDfmM58+9IjJqIxGV4bA4r2EtB/Q1LDNELIJmpLLyA5NrSvECxCqzLu7jNBlWgmA== + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"