diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index bc69eca527..062b08790c 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -22,7 +22,7 @@ jobs: with: node-version: 16 - uses: bahmutov/npm-install@v1 - - run: npm run lint:ts -w packages/website + - run: npm run lint -w packages/website test-e2e: name: "Test e2e - ${{ matrix.browser }}" diff --git a/package.json b/package.json index ef0dea5adc..a5e3ab8069 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,12 @@ "web": "npm start -w packages/website", "test": "npm run lint && npm run test --workspaces", "test:e2e": "npm run test:e2e --if-present --workspaces", - "lint": "standard --verbose | snazzy", - "lint:fix": "standard --fix " + "lint": "npm-run-all -p lint:workspaces lint:standard", + "lint:workspaces": "npm -ws run --if-present lint", + "lint:fix": "npm-run-all lint:standard:fix lint:fix:workspaces", + "lint:fix:workspaces": "npm -ws run --if-present lint:fix", + "lint:standard": "standard --verbose | snazzy", + "lint:standard:fix": "standard --fix" }, "devDependencies": { "npm-run-all": "^4.1.5", diff --git a/packages/website/.eslintignore b/packages/website/.eslintignore index e5215e032a..881209e564 100644 --- a/packages/website/.eslintignore +++ b/packages/website/.eslintignore @@ -1,6 +1,5 @@ # Specific to .eslintignore # Don't lint files we do not commit: Below are same as .gitignore -lib # dependencies /node_modules diff --git a/packages/website/components/card/card.js b/packages/website/components/card/card.js index baaa328c6c..cd710a2ddc 100644 --- a/packages/website/components/card/card.js +++ b/packages/website/components/card/card.js @@ -47,14 +47,6 @@ export default function Card({ card, cardsGroup = [], index = 0, targetClass, on countly.trackCustomLinkClick(countly.events.LINK_CLICK_EXPLORE_DOCS, e.currentTarget); }, []); - const handleKeySelect = useCallback( - (e, url) => { - onLinkClick(e); - router.push(url); - }, - [router, onLinkClick] - ); - const handleButtonClick = useCallback( cta => { if (cta.url) { diff --git a/packages/website/lib/api.js b/packages/website/lib/api.js index 793d80ab84..79e9096b21 100644 --- a/packages/website/lib/api.js +++ b/packages/website/lib/api.js @@ -178,7 +178,7 @@ export async function getUploads({ size, before, sortBy, sortOrder }) { 'Content-Type': 'application/json', Authorization: 'Bearer ' + (await getToken()), }, - }) + }); if (!res.ok) { throw new Error(`failed to get uploads: ${await res.text()}`); @@ -260,10 +260,10 @@ export async function listPins(status, token) { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token, // **** this needs to be a token generated from the tokens context }, - }) + }); if (!res.ok) { - throw new Error(`failed to get pinned files: ${await res.text()}`) + throw new Error(`failed to get pinned files: ${await res.text()}`); } - return res.json() + return res.json(); } diff --git a/packages/website/lib/constants.js b/packages/website/lib/constants.js index 2f459fb219..20f6e5ab32 100644 --- a/packages/website/lib/constants.js +++ b/packages/website/lib/constants.js @@ -1,16 +1,16 @@ -const API = /** @type {string} **/ (process.env.NEXT_PUBLIC_API) -const MAGIC_TOKEN = /** @type {string} **/ (process.env.NEXT_PUBLIC_MAGIC) +const API = /** @type {string} **/ (process.env.NEXT_PUBLIC_API); +const MAGIC_TOKEN = /** @type {string} **/ (process.env.NEXT_PUBLIC_MAGIC); // In dev, set these vars in a .env file in the parent monorepo project root. if (!API) { - throw new Error('MISSING ENV. Please set NEXT_PUBLIC_API') + throw new Error('MISSING ENV. Please set NEXT_PUBLIC_API'); } if (!MAGIC_TOKEN) { - throw new Error('MISSING ENV. Please set NEXT_PUBLIC_MAGIC') + throw new Error('MISSING ENV. Please set NEXT_PUBLIC_MAGIC'); } export default { API: API, MAGIC_TOKEN: MAGIC_TOKEN, - MAGIC_TOKEN_LIFESPAN: 900_000 // 15 mins -} + MAGIC_TOKEN_LIFESPAN: 900_000, // 15 mins +}; diff --git a/packages/website/lib/countly.js b/packages/website/lib/countly.js index a47963b249..236cc69a7a 100644 --- a/packages/website/lib/countly.js +++ b/packages/website/lib/countly.js @@ -1,12 +1,12 @@ -import { useEffect } from 'react' -import countly from 'countly-sdk-web' -import Router from 'next/router' - +/* eslint-disable */ +import { useEffect } from 'react'; +import countly from 'countly-sdk-web'; +import Router from 'next/router'; const config = { key: process.env.NEXT_PUBLIC_COUNTLY_KEY, - url: process.env.NEXT_PUBLIC_COUNTLY_URL -} + url: process.env.NEXT_PUBLIC_COUNTLY_URL, +}; /** @constant */ export const events = { @@ -29,7 +29,7 @@ export const events = { FEEDBACK_HELPFUL: 'feedbackHelpful', FEEDBACK_IMPORTANT: 'feedbackImportant', FEEDBACK_GENERAL: 'feedbackGeneral', -} +}; /** @constant */ export const ui = { @@ -44,110 +44,110 @@ export const ui = { TOKENS_EMPTY: 'tokens/empty', PROFILE_GETTING_STARTED: 'profile/getting-started', PROFILE_API_TOKENS: 'profile/api-tokens', -} +}; /** * Initialize countly analytics object */ -export function init () { +export function init() { if (typeof window === 'undefined') { - return + return; } - + if (ready) { - return + return; } - + if (!config.key || !config.url) { - console.warn('[lib/countly]', 'Countly config not found.') + console.warn('[lib/countly]', 'Countly config not found.'); - return + return; } - - countly.init({ app_key: config.key, url: config.url, debug: false }) - - countly.track_sessions() - countly.track_pageview() - countly.track_clicks() - countly.track_links() - countly.track_scrolls() - ready = true + countly.init({ app_key: config.key, url: config.url, debug: false }); + + countly.track_sessions(); + countly.track_pageview(); + countly.track_clicks(); + countly.track_links(); + countly.track_scrolls(); + + ready = true; // Track other not-so-easy to access links // NFT.Storage Banner link - document.querySelector('div > a[href*="https://nft.storage"]')?.addEventListener('click', (event) => { - const target = /** @type {HTMLLinkElement} **/ (event?.currentTarget) - + document.querySelector('div > a[href*="https://nft.storage"]')?.addEventListener('click', event => { + const target = /** @type {HTMLLinkElement} **/ (event?.currentTarget); + trackEvent(events.LINK_CLICK_BANNER_NFTSTORAGE, { link: target?.href, text: target?.innerText, - }) - }) + }); + }); } /** * Track an event to countly with custom data * - * @param {string} event Event name to be sent to countly. + * @param {string} event Event name to be sent to countly. * @param {Object} [segmentation] Custom data object to be used as segmentation data in countly. -*/ -export function trackEvent (event, segmentation = {}) { + */ +export function trackEvent(event, segmentation = {}) { if (!ready) { - init() + init(); } - ready && countly.add_event({ - key: event, - segmentation: { - path: location.pathname, - ...segmentation, - } - }) + ready && + countly.add_event({ + key: event, + segmentation: { + path: location.pathname, + ...segmentation, + }, + }); } /** * Track page view to countly. - * + * * @param {string} [path] Page route to track. Defaults to window.location.pathname if not provided. -*/ -export function trackPageView (path) { + */ +export function trackPageView(path) { if (!ready) { - init() + init(); } - ready && countly.track_pageview(path) + ready && countly.track_pageview(path); } /** * Track custom link click. - * - * @param {string} event Event name to be sent to countly. + * + * @param {string} event Event name to be sent to countly. * @param {HTMLLinkElement} target DOM element target of the clicked link. -*/ -export function trackCustomLinkClick (event, target) { + */ +export function trackCustomLinkClick(event, target) { if (!ready) { - init() + init(); } - ready && trackEvent(event, { - link: target.href.includes(location.origin) ? - new URL(target.href).pathname : - target.href, - text: target.innerText - }) + ready && + trackEvent(event, { + link: target.href.includes(location.origin) ? new URL(target.href).pathname : target.href, + text: target.innerText, + }); } -export function useCountly () { +export function useCountly() { useEffect(() => { - init() - Router.events.on('routeChangeComplete', (route) => { - trackPageView(route) - }) - }, []) + init(); + Router.events.on('routeChangeComplete', route => { + trackPageView(route); + }); + }, []); } -export let ready = false +export let ready = false; export default { events, @@ -156,5 +156,5 @@ export default { trackEvent, trackPageView, trackCustomLinkClick, - ready -} + ready, +}; diff --git a/packages/website/lib/floater-animations.js b/packages/website/lib/floater-animations.js index 272cc1687f..8840ed0f82 100644 --- a/packages/website/lib/floater-animations.js +++ b/packages/website/lib/floater-animations.js @@ -1,5 +1,5 @@ -export const initFloaterAnimations = async (scenes) => { - if (typeof window !== undefined) { +export const initFloaterAnimations = async scenes => { + if (typeof window !== 'undefined') { const ScrollMagic = (await import('scrollmagic')).default; const controller = new ScrollMagic.Controller(); const scrollMagicScenes = []; @@ -7,44 +7,44 @@ export const initFloaterAnimations = async (scenes) => { for (let i = 0; i < scenes.length; i++) { let scene = scenes[i]; scrollMagicScenes[i] = new ScrollMagic.Scene({ - triggerElement: "#" + scene.trigger, - triggerHook: "onEnter", + triggerElement: '#' + scene.trigger, + triggerHook: 'onEnter', offset: scene.offset ? scene.offset : 0, - duration: scene.duration - }) + duration: scene.duration, + }); } controller.addScene(scrollMagicScenes); - const addSceneAnimation = (i) => { + const addSceneAnimation = i => { const len = scenes[i].floaters.length; for (let j = 0; j < len; j++) { - const xi = scenes[i].floaters[j].start.x - const xf = scenes[i].floaters[j].end.x - const yi = scenes[i].floaters[j].start.y - const yf = scenes[i].floaters[j].end.y - const si = scenes[i].floaters[j].start.scale - const sf = scenes[i].floaters[j].end.scale - const ri = scenes[i].floaters[j].start.rotate - const rf = scenes[i].floaters[j].end.rotate - const transform = scenes[i].floaters[j].default ? scenes[i].floaters[j].default : '' - const id = scenes[i].floaters[j].id + const xi = scenes[i].floaters[j].start.x; + const xf = scenes[i].floaters[j].end.x; + const yi = scenes[i].floaters[j].start.y; + const yf = scenes[i].floaters[j].end.y; + const si = scenes[i].floaters[j].start.scale; + const sf = scenes[i].floaters[j].end.scale; + const ri = scenes[i].floaters[j].start.rotate; + const rf = scenes[i].floaters[j].end.rotate; + const transform = scenes[i].floaters[j].default ? scenes[i].floaters[j].default : ''; + const id = scenes[i].floaters[j].id; - scrollMagicScenes[i].on('progress', (e) => { - const element = document.getElementById(id); + scrollMagicScenes[i].on('progress', e => { + const element = document.getElementById(id); - if (element && !window.matchMedia(`(max-width: 40rem)`).matches) { - const t = e.progress; - const x = xi && xf ? (xf - xi) * t + xi : 0 - const y = yi && yf ? (yf - yi) * t + yi : 0 - const translate = x && y ? `translate(${x}px, ${y}px)` : '' - const scale = si && sf ? `scale(${(sf - si) * t + si})` : '' - const rotate = ri && rf ? `rotate(${(rf - ri) * t + ri}deg)` : '' + if (element && !window.matchMedia(`(max-width: 40rem)`).matches) { + const t = e.progress; + const x = xi && xf ? (xf - xi) * t + xi : 0; + const y = yi && yf ? (yf - yi) * t + yi : 0; + const translate = x && y ? `translate(${x}px, ${y}px)` : ''; + const scale = si && sf ? `scale(${(sf - si) * t + si})` : ''; + const rotate = ri && rf ? `rotate(${(rf - ri) * t + ri}deg)` : ''; - element.style.transform = `${transform} ${translate} ${scale} ${rotate}`; - } + element.style.transform = `${transform} ${translate} ${scale} ${rotate}`; + } }); } - } + }; for (let i = 0; i < scenes.length; i++) { addSceneAnimation(i); diff --git a/packages/website/lib/formatter.js b/packages/website/lib/formatter.js index 5fa02de7f2..732ddf5a90 100644 --- a/packages/website/lib/formatter.js +++ b/packages/website/lib/formatter.js @@ -1,14 +1,14 @@ -import filesz from 'filesize' +import filesz from 'filesize'; /** @type {object} */ const defaultOptions = { base: 2, - standard: "iec" -} + standard: 'iec', +}; /** * local filesize formatter abstraction with default options * * @param {any} value */ -export const fileSize = (value) => filesz(value, defaultOptions) \ No newline at end of file +export const fileSize = value => filesz(value, defaultOptions); diff --git a/packages/website/lib/magic.js b/packages/website/lib/magic.js index b8ea60347e..ee0a454293 100644 --- a/packages/website/lib/magic.js +++ b/packages/website/lib/magic.js @@ -1,20 +1,21 @@ -import { Magic } from 'magic-sdk' -import { OAuthExtension } from '@magic-ext/oauth' -import constants from './constants' +import { Magic } from 'magic-sdk'; +import { OAuthExtension } from '@magic-ext/oauth'; -const API = constants.API +import constants from './constants'; + +const API = constants.API; /** @type {import('magic-sdk').Magic | null} */ -let magic = null +let magic = null; export function getMagic() { if (magic) { - return magic + return magic; } magic = new Magic(constants.MAGIC_TOKEN, { extensions: [new OAuthExtension()], - }) + }); - return magic + return magic; } /** @@ -33,15 +34,15 @@ export async function login(token, type = 'magic', data = {}) { type, data, }), - }) + }); if (!res.ok) { - throw new Error(`failed to login user: ${await res.text()}`) + throw new Error(`failed to login user: ${await res.text()}`); } - return res.json() + return res.json(); } export async function isLoggedIn() { - return getMagic().user.isLoggedIn() + return getMagic().user.isLoggedIn(); } /** @@ -53,14 +54,14 @@ export async function loginEmail(email) { const didToken = await getMagic().auth.loginWithMagicLink({ email: email, redirectURI: new URL('/callback/', window.location.origin).href, - }) + }); if (didToken) { - const data = await login(didToken) - return data + const data = await login(didToken); + return data; } - throw new Error('Login failed.') + throw new Error('Login failed.'); } /** @@ -73,32 +74,32 @@ export async function loginSocial(provider) { await getMagic().oauth.loginWithRedirect({ provider, redirectURI: new URL('/callback/', window.location.origin).href, - }) + }); } export async function redirectMagic() { - const idToken = await getMagic().auth.loginWithCredential() + const idToken = await getMagic().auth.loginWithCredential(); if (idToken) { try { - const data = await login(idToken) - return { ...data, idToken } + const data = await login(idToken); + return { ...data, idToken }; } catch (err) { - await getMagic().user.logout() - throw err + await getMagic().user.logout(); + throw err; } } - throw new Error('Login failed.') + throw new Error('Login failed.'); } export async function redirectSocial() { // @ts-ignore - TODO fix Magic extension types - const result = await getMagic().oauth.getRedirectResult() + const result = await getMagic().oauth.getRedirectResult(); try { - const data = await login(result.magic.idToken, 'github', result) - return { ...data, idToken: result.magic.idToken } + const data = await login(result.magic.idToken, 'github', result); + return { ...data, idToken: result.magic.idToken }; } catch (err) { - await getMagic().user.logout() - throw err + await getMagic().user.logout(); + throw err; } } diff --git a/packages/website/lib/statuspage-api.js b/packages/website/lib/statuspage-api.js index 796c4d5179..d4e877d0fa 100644 --- a/packages/website/lib/statuspage-api.js +++ b/packages/website/lib/statuspage-api.js @@ -1,15 +1,13 @@ -export const API = 'https://status.web3.storage/api/v2' +export const API = 'https://status.web3.storage/api/v2'; export async function getStatusPageSummary() { - const route = '/summary.json' - const res = await fetch(API + route) - const body = await res.json() + const route = '/summary.json'; + const res = await fetch(API + route); + const body = await res.json(); if (body) { - return body + return body; } else { - throw new Error( - 'An error occurred while trying to fetch data from the status page API.' - ) + throw new Error('An error occurred while trying to fetch data from the status page API.'); } } diff --git a/packages/website/lib/types.d.ts b/packages/website/lib/types.d.ts index 055ec607fd..170a7bbbeb 100644 --- a/packages/website/lib/types.d.ts +++ b/packages/website/lib/types.d.ts @@ -1,7 +1,7 @@ -declare module 'countly-sdk-web' +declare module 'countly-sdk-web'; -declare module "\*.svg" { - import React = require("react"); +declare module '*.svg' { + import React = require('react'); export const ReactComponent: React.SFC>; const src: string; export default src; diff --git a/packages/website/lib/user.js b/packages/website/lib/user.js index 2b7197b6ed..f1839f76f3 100644 --- a/packages/website/lib/user.js +++ b/packages/website/lib/user.js @@ -1,8 +1,9 @@ -import { useEffect } from 'react' -import { isLoggedIn } from './magic.js' -import { useQuery } from 'react-query' -import { useRouter } from 'next/router' -import constants from './constants.js' +import { useEffect } from 'react'; +import { useQuery } from 'react-query'; +import { useRouter } from 'next/router'; + +import { isLoggedIn } from './magic.js'; +import constants from './constants.js'; /** * User Logged In Hook @@ -14,16 +15,22 @@ import constants from './constants.js' * @returns */ export function useLoggedIn({ redirectTo, redirectIfFound, enabled } = {}) { - const router = useRouter() - const { status, data: loggedIn, error, isFetching, isLoading } = useQuery('magic-user', isLoggedIn, { + const router = useRouter(); + const { + status, + data: loggedIn, + error, + isFetching, + isLoading, + } = useQuery('magic-user', isLoggedIn, { staleTime: constants.MAGIC_TOKEN_LIFESPAN, refetchOnWindowFocus: false, - refetchOnReconnect: false - }) + refetchOnReconnect: false, + }); useEffect(() => { if (!redirectTo || isLoading || isFetching) { - return + return; } if ( // If redirectTo is set, redirect if the user was not found. @@ -31,9 +38,9 @@ export function useLoggedIn({ redirectTo, redirectIfFound, enabled } = {}) { // If redirectIfFound is also set, redirect if the user was found (redirectIfFound && loggedIn) ) { - router.push(redirectTo) + router.push(redirectTo); } - }, [redirectTo, redirectIfFound, status, isFetching, isLoading, loggedIn, router, enabled]) + }, [redirectTo, redirectIfFound, status, isFetching, isLoading, loggedIn, router, enabled]); - return { status, isLoggedIn: loggedIn, error, isFetching, isLoading } + return { status, isLoggedIn: loggedIn, error, isFetching, isLoading }; } diff --git a/packages/website/lib/utils.js b/packages/website/lib/utils.js index a3244ccead..382ce6a43b 100644 --- a/packages/website/lib/utils.js +++ b/packages/website/lib/utils.js @@ -29,7 +29,8 @@ export const formatTimestampFull = timestamp => { * @param {string} string The string being truncated * @param {number} [len] The max length allowed * @param {string} [end] The copy used to truncate at the end - * @param {'single'|'double'} [type] The type of truncation to use, `single` puts ellipses at the end, `double` in the middle + * @param {'single'|'double'} [type] The type of truncation to use, `single` puts ellipses at the end, + * `double` in the middle * @returns {string} */ export const truncateString = (string, len = 30, end = '...', type = 'single') => { @@ -45,19 +46,19 @@ export const truncateString = (string, len = 30, end = '...', type = 'single') = * * @param {string} text Text to be copied to clipboard */ -export const addTextToClipboard = (text) => { - const container = document.createElement('textarea') - container.style.position = 'fixed' - container.style.left = '-99999px' - container.style.zIndex = '-1' - container.style.opacity = '0' - container.style.pointerEvents = 'none' - container.innerHTML = text - document.body.appendChild(container) - container.select() - document.execCommand('copy') - document.body.removeChild(container) -} +export const addTextToClipboard = text => { + const container = document.createElement('textarea'); + container.style.position = 'fixed'; + container.style.left = '-99999px'; + container.style.zIndex = '-1'; + container.style.opacity = '0'; + container.style.pointerEvents = 'none'; + container.innerHTML = text; + document.body.appendChild(container); + container.select(); + document.execCommand('copy'); + document.body.removeChild(container); +}; /** * Utility function to standardize element heights based on largest sibling @@ -67,29 +68,28 @@ export const addTextToClipboard = (text) => { */ export const standardizeSiblingHeights = (target, reset) => { if (typeof document !== 'undefined') { - const elements = ( + const elements = /** @type {HTMLCollectionOf} */ - (document.getElementsByClassName(target)) - ); - const heights = [] + (document.getElementsByClassName(target)); + const heights = []; for (let i = 0; i < elements.length; i++) { - const el = /** @type {HTMLElement} */ (elements[i]) + const el = /** @type {HTMLElement} */ (elements[i]); if (reset) { - el.style.minHeight = 'unset' + el.style.minHeight = 'unset'; } - const rect = el.getBoundingClientRect() - heights.push(rect.height) + const rect = el.getBoundingClientRect(); + heights.push(rect.height); } const max = Math.max(...heights); for (let i = 0; i < elements.length; i++) { - const el = /** @type {HTMLElement} */ (elements[i]) - el.style.minHeight = max + 'px' + const el = /** @type {HTMLElement} */ (elements[i]); + el.style.minHeight = max + 'px'; } } -} +}; /** * Utility to check if element is in viewport @@ -97,10 +97,10 @@ export const standardizeSiblingHeights = (target, reset) => { * @param {any} element element to test * @returns {boolean} */ -export const elementIsInViewport = (element) => { +export const elementIsInViewport = element => { if (element && typeof window !== 'undefined' && typeof document !== 'undefined') { const rect = element.getBoundingClientRect(); - return (rect.top >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)) + return rect.top >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight); } - return false -} + return false; +}; diff --git a/packages/website/package.json b/packages/website/package.json index ac7b03e617..81a9da5e63 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -12,10 +12,11 @@ "export": "next export", "test": "eslint './**/*.js' && tsc --build", "test:e2e": "npx playwright test", - "lint": "npm run lint:ts && npm run lint:es:fix", + "lint": "npm run lint:ts && npm run lint:es", + "lint:fix": "npm run lint:es:fix", "lint:ts": "tsc --noEmit", - "lint:es": "eslint '{pages,components,hooks,content,store}/**/*.{js,ts,tsx}' --max-warnings=0", - "lint:es:fix": "eslint '{pages,components,hooks,content,store}/**/*.{js,ts,tsx}' --fix --max-warnings=0", + "lint:es": "eslint --max-warnings=0", + "lint:es:fix": "npm run lint:es -- --fix", "lint-staged": "lint-staged", "storybook": "start-storybook -p 9000", "build-storybook": "build-storybook", diff --git a/packages/website/pages/tokens/index.js b/packages/website/pages/tokens/index.js index cf7faf6e5b..fe9672e1ef 100644 --- a/packages/website/pages/tokens/index.js +++ b/packages/website/pages/tokens/index.js @@ -1,7 +1,6 @@ import clsx from 'clsx'; import { useEffect, useState } from 'react'; -import Link from 'components/link/link'; import TokenCreator from 'components/tokens/tokenCreator/tokenCreator'; import TokensManager from 'components/tokens/tokensManager/tokensManager'; import Button, { ButtonVariant } from 'components/button/button';