From 151c26dfd3b0c8b24a2ae4c69ec50abdc793a2cb Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Mon, 16 Oct 2023 12:28:16 +0800 Subject: [PATCH] Migrate landing page to app dir (#1587) * app dir yay * better docs route * fix icon sizing * mobile sidebars * detect webgl in useEffect * separate zxcvbn --- apps/desktop/src/App.tsx | 2 +- apps/storybook/.storybook/preview.ts | 2 +- apps/web/src/index.tsx | 2 +- interface/components/PasswordMeter.tsx | 21 ++++++++++-- interface/package.json | 3 +- packages/assets/package.json | 1 + packages/assets/svgs/brands/index.ts | 2 ++ packages/client/package.json | 1 + packages/ui/package.json | 1 + packages/ui/src/Button.tsx | 10 +++--- packages/ui/src/CheckBox.tsx | 2 ++ packages/ui/src/ContextMenu.tsx | 2 ++ packages/ui/src/Dialog.tsx | 2 ++ packages/ui/src/Dropdown.tsx | 2 ++ packages/ui/src/DropdownMenu.tsx | 2 ++ packages/ui/src/Input.tsx | 2 ++ packages/ui/src/Popover.tsx | 2 ++ packages/ui/src/ProgressBar.tsx | 2 ++ packages/ui/src/RadioGroup.tsx | 2 ++ packages/ui/src/Select.tsx | 2 ++ packages/ui/src/Slider.tsx | 2 ++ packages/ui/src/Switch.tsx | 2 ++ packages/ui/src/Tabs.tsx | 2 ++ packages/ui/src/Toast.tsx | 2 ++ packages/ui/src/Tooltip.tsx | 2 ++ packages/ui/src/forms/InputField.tsx | 44 +++++++++++++++++-------- packages/ui/src/forms/index.ts | 2 ++ packages/ui/src/forms/zxcvbn.ts | 3 ++ pnpm-lock.yaml | Bin 843788 -> 831708 bytes 29 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 packages/ui/src/forms/zxcvbn.ts diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 9b944f712..faf91b29d 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -20,7 +20,7 @@ import { } from '@sd/interface'; import { getSpacedropState } from '@sd/interface/hooks/useSpacedropState'; -import '@sd/ui/style'; +import '@sd/ui/style/style.scss'; import * as commands from './commands'; import { createUpdater } from './updater'; diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index 1384b87e1..eb1ab4236 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -1,6 +1,6 @@ import type { Preview } from '@storybook/react'; -import '@sd/ui/style'; +import '@sd/ui/style.scss'; const preview: Preview = { parameters: { diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index fd460a22c..d4c554b75 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -2,7 +2,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; -import '@sd/ui/style'; +import '@sd/ui/style/style.scss'; import '~/patches'; import App from './App'; diff --git a/interface/components/PasswordMeter.tsx b/interface/components/PasswordMeter.tsx index 5f7c6aa72..0ff466d6c 100644 --- a/interface/components/PasswordMeter.tsx +++ b/interface/components/PasswordMeter.tsx @@ -1,12 +1,29 @@ import clsx from 'clsx'; -import { getPasswordStrength } from '@sd/client'; +import { useEffect, useState } from 'react'; +import { type getPasswordStrength } from '@sd/client'; export interface PasswordMeterProps { password: string; } export const PasswordMeter = (props: PasswordMeterProps) => { - const { score, scoreText } = getPasswordStrength(props.password); + const [getStrength, setGetStrength] = useState(); + const { score, scoreText } = getStrength + ? getStrength(props.password) + : { score: 0, scoreText: 'Loading...' }; + + useEffect(() => { + let cancelled = false; + + import('@sd/client').then(({ getPasswordStrength }) => { + if (cancelled) return; + setGetStrength(() => getPasswordStrength); + }); + + return () => { + cancelled = true; + }; + }, []); return (
diff --git a/interface/package.json b/interface/package.json index f59d715e6..6da3afa45 100644 --- a/interface/package.json +++ b/interface/package.json @@ -3,6 +3,7 @@ "private": true, "main": "index.tsx", "types": "index.tsx", + "sideEffects": false, "scripts": { "lint": "eslint . --cache", "typecheck": "tsc -b" @@ -10,7 +11,7 @@ "dependencies": { "@fontsource/inter": "^4.5.13", "@headlessui/react": "^1.7.3", - "@icons-pack/react-simple-icons": "^7.2.0", + "@icons-pack/react-simple-icons": "^9.1.0", "@phosphor-icons/react": "^2.0.10", "@radix-ui/react-progress": "^1.0.1", "@radix-ui/react-slider": "^1.1.0", diff --git a/packages/assets/package.json b/packages/assets/package.json index 1ace082cf..09330f2b6 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "license": "GPL-3.0-only", "private": true, + "sideEffects": false, "scripts": { "gen": "node ./scripts/generate.mjs" } diff --git a/packages/assets/svgs/brands/index.ts b/packages/assets/svgs/brands/index.ts index 7c774c76c..ae8850504 100644 --- a/packages/assets/svgs/brands/index.ts +++ b/packages/assets/svgs/brands/index.ts @@ -3,6 +3,8 @@ * To regenerate this file, run: pnpm assets gen */ +'use client'; + import { ReactComponent as Academia } from './Academia.svg'; import { ReactComponent as Apple } from './apple.svg'; import { ReactComponent as Discord } from './Discord.svg'; diff --git a/packages/client/package.json b/packages/client/package.json index ad49e7662..13ddb430a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,7 @@ { "name": "@sd/client", "private": true, + "sideEffects": false, "main": "./src/index.ts", "types": "./src/index.ts", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 643162b20..10df02533 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,6 +4,7 @@ "license": "GPL-3.0-only", "main": "src/index.ts", "types": "src/index.ts", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./src/forms": "./src/forms/index.ts", diff --git a/packages/ui/src/Button.tsx b/packages/ui/src/Button.tsx index 53016bc80..87712393b 100644 --- a/packages/ui/src/Button.tsx +++ b/packages/ui/src/Button.tsx @@ -1,9 +1,11 @@ +'use client'; + import { cva, cx, VariantProps } from 'class-variance-authority'; import clsx from 'clsx'; import { ComponentProps, forwardRef } from 'react'; import { Link } from 'react-router-dom'; -export interface ButtonBaseProps extends VariantProps {} +export interface ButtonBaseProps extends VariantProps {} export type ButtonProps = ButtonBaseProps & React.ButtonHTMLAttributes & { @@ -22,7 +24,7 @@ type Button = { const hasHref = (props: ButtonProps | LinkButtonProps): props is LinkButtonProps => 'href' in props; -export const styles = cva( +export const buttonStyles = cva( [ 'cursor-default items-center rounded-md border outline-none transition-colors duration-100', 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-70', @@ -73,7 +75,7 @@ export const Button = forwardRef< HTMLButtonElement | HTMLAnchorElement, ButtonProps | LinkButtonProps >(({ className, ...props }, ref) => { - className = cx(styles(props), className); + className = cx(buttonStyles(props), className); return hasHref(props) ? ( ) : ( @@ -88,7 +90,7 @@ export const ButtonLink = forwardRef< return ( { + const [zxcvbn, setZxcvbn] = useState(); + const [strength, setStrength] = useState<{ label: string; score: number }>(); - const updateStrength = useDebouncedCallback( - () => setStrength(props.password ? getPasswordStrength(props.password) : undefined), - 100 - ); + const updateStrength = useDebouncedCallback(() => { + if (!zxcvbn) return; + + setStrength(props.password ? getPasswordStrength(props.password, zxcvbn) : undefined); + }, 100); // TODO: Remove duplicate in @sd/client - function getPasswordStrength(password: string): { label: string; score: number } { + function getPasswordStrength( + password: string, + zxcvbn: typeof import('./zxcvbn') + ): { label: string; score: number } { const ratings = ['Poor', 'Weak', 'Good', 'Strong', 'Perfect']; - zxcvbnOptions.setOptions({ + zxcvbn.zxcvbnOptions.setOptions({ dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary + ...zxcvbn.languageCommon.dictionary, + ...zxcvbn.languageEn.dictionary }, - graphs: zxcvbnCommonPackage.adjacencyGraphs, - translations: zxcvbnEnPackage.translations + graphs: zxcvbn.languageCommon.adjacencyGraphs, + translations: zxcvbn.languageEn.translations }); - const result = zxcvbn(password); + const result = zxcvbn.zxcvbn(password); return { label: ratings[result.score]!, score: result.score }; } + useEffect(() => { + let cancelled = false; + + import('./zxcvbn').then((zxcvbn) => { + if (cancelled) return; + setZxcvbn(zxcvbn); + }); + + return () => { + cancelled = true; + }; + }, []); + useEffect(() => updateStrength(), [props.password, updateStrength]); return ( diff --git a/packages/ui/src/forms/index.ts b/packages/ui/src/forms/index.ts index f1b0715b0..4c93d3e33 100644 --- a/packages/ui/src/forms/index.ts +++ b/packages/ui/src/forms/index.ts @@ -1,3 +1,5 @@ +'use client'; + export * from './Form'; export * from './FormField'; export * from './CheckBoxField'; diff --git a/packages/ui/src/forms/zxcvbn.ts b/packages/ui/src/forms/zxcvbn.ts new file mode 100644 index 000000000..c382a2f3d --- /dev/null +++ b/packages/ui/src/forms/zxcvbn.ts @@ -0,0 +1,3 @@ +export { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'; +export { default as languageCommon } from '@zxcvbn-ts/language-common'; +export { default as languageEn } from '@zxcvbn-ts/language-en'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 792d05f9a61a4603f43a1bea310b0067cb2a59f9..ba4b3a411494350523a29892b4497b39bada20d2 100644 GIT binary patch delta 3698 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