mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
finish base jotai structure + firestore with suspense
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ cloud_functions/functions/lib
|
||||
*.firebaserc
|
||||
*-firebase.json
|
||||
.firebase/
|
||||
*.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
cd www
|
||||
yarn lint-staged
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { whenProd } = require("@craco/craco");
|
||||
const { whenDev } = require("@craco/craco");
|
||||
const CracoAlias = require("craco-alias");
|
||||
const CracoSwcPlugin = require("craco-swc");
|
||||
|
||||
@@ -12,10 +12,12 @@ module.exports = {
|
||||
tsConfigPath: "./tsconfig.extend.json",
|
||||
},
|
||||
},
|
||||
// Use swc on production only since Jotai doesn’t have swc plugins yet
|
||||
// Use Babel on dev since Jotai doesn’t have swc plugins yet
|
||||
// See https://github.com/pmndrs/jotai/discussions/1057
|
||||
...whenProd(
|
||||
() => [
|
||||
// Use swc on production and test since Babel seems to break Jest
|
||||
...whenDev(
|
||||
() => [],
|
||||
[
|
||||
{
|
||||
plugin: CracoSwcPlugin,
|
||||
options: {
|
||||
@@ -31,11 +33,16 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
]
|
||||
),
|
||||
],
|
||||
babel: {
|
||||
plugins: ["jotai/babel/plugin-debug-label"],
|
||||
},
|
||||
jest: {
|
||||
configure: (jestConfig) => {
|
||||
jestConfig.moduleNameMapper["^lodash-es$"] = "lodash";
|
||||
return jestConfig;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
45
package.json
45
package.json
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"name": "rowy",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"homepage": "https://rowy.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rowyio/rowy.git"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
@@ -10,27 +15,31 @@
|
||||
"@mui/lab": "^5.0.0-alpha.76",
|
||||
"@mui/material": "^5.6.0",
|
||||
"@mui/styles": "^5.6.0",
|
||||
"@rowy/multiselect": "^0.2.3",
|
||||
"date-fns": "^2.28.0",
|
||||
"dompurify": "^2.3.6",
|
||||
"firebase": "^9.6.10",
|
||||
"firebaseui": "^6.0.1",
|
||||
"jotai": "^1.6.2",
|
||||
"jotai": "^1.6.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.0.0",
|
||||
"react-data-grid": "7.0.0-beta.5",
|
||||
"react-div-100vh": "^0.7.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-firebaseui": "^6.0.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^5.0.0",
|
||||
"reactfire": "^4.2.1",
|
||||
"typescript": "^4.6.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=7699 craco start",
|
||||
"startWithEmulator": "cross-env PORT=7699 REACT_APP_FIREBASE_EMULATOR=true craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "craco eject",
|
||||
"analyze": "source-map-explorer ./build/static/js/*.js",
|
||||
"prepare": "husky install",
|
||||
"env": "node createDotEnv",
|
||||
"target": "firebase target:apply hosting rowy",
|
||||
@@ -40,11 +49,24 @@
|
||||
"node": ">=16"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"plugins": [
|
||||
"eslint-plugin-no-relative-import-paths",
|
||||
"eslint-plugin-tsdoc"
|
||||
],
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest",
|
||||
"prettier"
|
||||
]
|
||||
],
|
||||
"rules": {
|
||||
"no-relative-import-paths/no-relative-import-paths": [
|
||||
"error",
|
||||
{
|
||||
"allowSameFolder": true
|
||||
}
|
||||
],
|
||||
"tsdoc/syntax": "warn"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -65,12 +87,15 @@
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.0",
|
||||
"@testing-library/user-event": "^14.0.4",
|
||||
"@types/dompurify": "^2.3.3",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react": "^18.0.5",
|
||||
"@types/react-div-100vh": "^0.4.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
@@ -80,9 +105,15 @@
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-config-react-app": "^7.0.0",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.2.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"husky": ">=7.0.4",
|
||||
"lint-staged": ">=12.3.7",
|
||||
"prettier": "^2.6.2"
|
||||
"prettier": "^2.6.2",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": "eslint --cache --fix",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -3,6 +3,6 @@ import App from "./App";
|
||||
|
||||
test("renders learn react link", () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
const linkElement = screen.getByText(/…/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
68
src/App.tsx
68
src/App.tsx
@@ -1,25 +1,55 @@
|
||||
import "./App.css";
|
||||
import DataGrid from "react-data-grid";
|
||||
import CloudLogs from "@src/assets/icons/CloudLogs";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
|
||||
const columns = [
|
||||
{ key: "id", name: "ID" },
|
||||
{ key: "title", name: "Title" },
|
||||
];
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import ErrorFallback from "@src/components/ErrorFallback";
|
||||
|
||||
const rows = [
|
||||
{ id: 0, title: "Example" },
|
||||
{ id: 1, title: "Demo" },
|
||||
];
|
||||
import { Provider } from "jotai";
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
import Loading from "@src/components/Loading";
|
||||
|
||||
function App() {
|
||||
import FirebaseProject from "@src/sources/ProjectSourceFirebase";
|
||||
import ThemeProvider from "@src/theme/ThemeProvider";
|
||||
|
||||
const AuthPage = lazy(
|
||||
() => import("@src/pages/Auth" /* webpackChunkName: "AuthPage" */)
|
||||
);
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<DataGrid columns={columns} rows={rows} />
|
||||
|
||||
<CloudLogs />
|
||||
</div>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<HelmetProvider>
|
||||
<Provider scope={globalScope}>
|
||||
<ThemeProvider>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Loading
|
||||
fullScreen
|
||||
message="FirebaseProject suspended"
|
||||
timeout={0}
|
||||
delay={0}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FirebaseProject />
|
||||
</Suspense>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Loading
|
||||
fullScreen
|
||||
message="AuthPage suspended"
|
||||
timeout={0}
|
||||
delay={0}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AuthPage />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</HelmetProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
5
src/atoms/auth.ts
Normal file
5
src/atoms/auth.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { atom } from "jotai";
|
||||
import type { User } from "firebase/auth";
|
||||
|
||||
export const currentUserAtom = atom<User | null | undefined>(undefined);
|
||||
export const userRolesAtom = atom<string[]>([]);
|
||||
1
src/atoms/globalScope.ts
Normal file
1
src/atoms/globalScope.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const globalScope = Symbol("globalScope");
|
||||
5
src/atoms/project.ts
Normal file
5
src/atoms/project.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { atom } from "jotai";
|
||||
import { DocumentData } from "firebase/firestore";
|
||||
|
||||
export const publicSettingsAtom = atom<DocumentData>({});
|
||||
export const projectSettingsAtom = atom<DocumentData>({});
|
||||
43
src/atoms/user.ts
Normal file
43
src/atoms/user.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { DocumentData } from "firebase/firestore";
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
import themes from "@src/theme";
|
||||
import { publicSettingsAtom } from "./project";
|
||||
|
||||
export const userSettingsAtom = atom<DocumentData>({});
|
||||
|
||||
export const themeAtom = atomWithStorage<"light" | "dark">(
|
||||
"__ROWY__THEME",
|
||||
"light"
|
||||
);
|
||||
export const themeOverriddenAtom = atomWithStorage(
|
||||
"__ROWY__THEME_OVERRIDDEN",
|
||||
false
|
||||
);
|
||||
|
||||
export const customizedThemesAtom = atom((get) => {
|
||||
const publicSettings = get(publicSettingsAtom);
|
||||
const userSettings = get(userSettingsAtom);
|
||||
|
||||
const lightCustomizations = merge(
|
||||
{},
|
||||
publicSettings.theme?.base,
|
||||
publicSettings.theme?.light,
|
||||
userSettings.theme?.base,
|
||||
userSettings.theme?.light
|
||||
);
|
||||
const darkCustomizations = merge(
|
||||
{},
|
||||
publicSettings.theme?.base,
|
||||
publicSettings.theme?.dark,
|
||||
userSettings.theme?.base,
|
||||
userSettings.theme?.dark
|
||||
);
|
||||
|
||||
return {
|
||||
light: themes.light(lightCustomizations),
|
||||
dark: themes.dark(darkCustomizations),
|
||||
};
|
||||
});
|
||||
19
src/components/CircularProgressOptical.tsx
Normal file
19
src/components/CircularProgressOptical.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { CircularProgress, CircularProgressProps } from "@mui/material";
|
||||
|
||||
export default function CircularProgressOptical({
|
||||
size = 40,
|
||||
...props
|
||||
}: CircularProgressProps & { size?: number }) {
|
||||
const DEFAULT_SIZE = 40;
|
||||
const DEFAULT_THICKNESS = 3.6;
|
||||
const linearThickness = (DEFAULT_SIZE / size) * DEFAULT_THICKNESS;
|
||||
const opticalRatio = 1 - (1 - size / DEFAULT_SIZE) / 2;
|
||||
|
||||
return (
|
||||
<CircularProgress
|
||||
{...props}
|
||||
size={size}
|
||||
thickness={linearThickness * opticalRatio}
|
||||
/>
|
||||
);
|
||||
}
|
||||
106
src/components/EmptyState.tsx
Normal file
106
src/components/EmptyState.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { use100vh } from "react-div-100vh";
|
||||
|
||||
import {
|
||||
Grid,
|
||||
GridProps,
|
||||
Stack,
|
||||
Typography,
|
||||
SvgIconTypeMap,
|
||||
} from "@mui/material";
|
||||
import { OverridableComponent } from "@mui/material/OverridableComponent";
|
||||
import ErrorIcon from "@mui/icons-material/ErrorOutline";
|
||||
|
||||
export interface IEmptyStateProps extends Partial<GridProps> {
|
||||
/** Primary message displayed under the icon */
|
||||
message?: React.ReactNode;
|
||||
/** Description text displayed under primary message */
|
||||
description?: React.ReactNode;
|
||||
/** Override icon component */
|
||||
Icon?: OverridableComponent<SvgIconTypeMap>;
|
||||
/** Set height to `100vh`. Default: `false` */
|
||||
fullScreen?: boolean;
|
||||
/** Basic inline presentation without padding. Default: `false` */
|
||||
basic?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an empty state message with sensible defaults.
|
||||
* By default, height is `100%`.
|
||||
* Override with props that are passed to the root MUI `Grid` component.
|
||||
*/
|
||||
export default function EmptyState({
|
||||
message = "Nothing here",
|
||||
description,
|
||||
Icon = ErrorIcon,
|
||||
fullScreen = false,
|
||||
basic = false,
|
||||
...props
|
||||
}: IEmptyStateProps) {
|
||||
const fullScreenHeight = use100vh() ?? "100vh";
|
||||
|
||||
if (basic)
|
||||
return (
|
||||
<Grid container alignItems="center" spacing={1} {...props}>
|
||||
<Grid item>
|
||||
<Icon style={{ display: "block" }} />
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
{message}
|
||||
{description && ": "}
|
||||
{description}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
{...props}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: fullScreen ? fullScreenHeight : "100%",
|
||||
textAlign: "center",
|
||||
...props.style,
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
sx={{
|
||||
maxWidth: "25em !important",
|
||||
width: (theme) => `calc(100% - ${theme.spacing(1 * 2)})`,
|
||||
px: 1,
|
||||
typography: "body2",
|
||||
|
||||
"& .icon": {
|
||||
color: "action.active",
|
||||
fontSize: "3rem",
|
||||
mx: "auto",
|
||||
display: "block",
|
||||
mb: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon className="icon" />
|
||||
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h6"
|
||||
gutterBottom
|
||||
style={{ cursor: "default" }}
|
||||
>
|
||||
{message}
|
||||
</Typography>
|
||||
|
||||
{description && (
|
||||
<Stack spacing={2} alignItems="center">
|
||||
{description}
|
||||
</Stack>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
61
src/components/ErrorFallback.tsx
Normal file
61
src/components/ErrorFallback.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { FallbackProps } from "react-error-boundary";
|
||||
|
||||
import { Button } from "@mui/material";
|
||||
import ReloadIcon from "@mui/icons-material/Refresh";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
|
||||
import EmptyState, { IEmptyStateProps } from "@src/components/EmptyState";
|
||||
import meta from "@root/package.json";
|
||||
|
||||
export interface IErrorFallbackProps extends FallbackProps, IEmptyStateProps {}
|
||||
|
||||
export default function ErrorFallback({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
...props
|
||||
}: IErrorFallbackProps) {
|
||||
if (error.message.startsWith("Loading chunk"))
|
||||
return (
|
||||
<EmptyState
|
||||
Icon={ReloadIcon}
|
||||
message="New update available"
|
||||
description={
|
||||
<>
|
||||
<span>Reload this page to get the latest update</span>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
startIcon={<ReloadIcon />}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
fullScreen
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EmptyState
|
||||
message="Something went wrong"
|
||||
description={
|
||||
<>
|
||||
<span>{error.message}</span>
|
||||
<Button
|
||||
href={
|
||||
meta.repository.url.replace(".git", "") + "/issues/new/choose"
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Report issue
|
||||
<InlineOpenInNewIcon />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
fullScreen
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
19
src/components/InlineOpenInNewIcon.tsx
Normal file
19
src/components/InlineOpenInNewIcon.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { styled } from "@mui/material";
|
||||
|
||||
export const InlineOpenInNewIcon = styled("span")(() => ({
|
||||
position: "relative",
|
||||
width: "1em",
|
||||
height: "1em",
|
||||
marginLeft: "0.25ch",
|
||||
display: "inline-block",
|
||||
verticalAlign: "baseline",
|
||||
|
||||
"&::after": {
|
||||
content: "'\\2197'",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
export default InlineOpenInNewIcon;
|
||||
52
src/components/Loading.tsx
Normal file
52
src/components/Loading.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { use100vh } from "react-div-100vh";
|
||||
|
||||
import { Fade, Stack, StackProps, Typography } from "@mui/material";
|
||||
import CircularProgressOptical from "@src/components/CircularProgressOptical";
|
||||
|
||||
interface ILoadingProps extends Partial<StackProps> {
|
||||
message?: string;
|
||||
fullScreen?: boolean;
|
||||
timeout?: number;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export default function Loading({
|
||||
message = "Loading",
|
||||
fullScreen = false,
|
||||
timeout = 1000,
|
||||
delay = 1000,
|
||||
...props
|
||||
}: ILoadingProps) {
|
||||
const fullScreenHeight = use100vh() ?? "100vh";
|
||||
|
||||
return (
|
||||
<Fade
|
||||
in
|
||||
timeout={timeout}
|
||||
style={{ transitionDelay: `${delay}ms` }}
|
||||
unmountOnExit
|
||||
>
|
||||
<Stack
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
{...props}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: fullScreen ? fullScreenHeight : "100%",
|
||||
alignItems: "center",
|
||||
...props.style,
|
||||
}}
|
||||
>
|
||||
<CircularProgressOptical />
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="div"
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
{message}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Fade>
|
||||
);
|
||||
}
|
||||
55
src/hooks/useFirestoreDocWithAtom.ts
Normal file
55
src/hooks/useFirestoreDocWithAtom.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAtom, PrimitiveAtom } from "jotai";
|
||||
import { Scope } from "jotai/core/atom";
|
||||
import { useUpdateAtom } from "jotai/utils";
|
||||
import {
|
||||
doc,
|
||||
DocumentData,
|
||||
onSnapshot,
|
||||
FirestoreError,
|
||||
} from "firebase/firestore";
|
||||
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase";
|
||||
|
||||
/**
|
||||
* Attaches a listener for Firestore documents and unsubscribes on unmount.
|
||||
* Gets the Firestore instance initiated in globalScope.
|
||||
* Updates an atom and suspends that atom until the first snapshot is received.
|
||||
*
|
||||
* @param dataAtom - Atom to store data in
|
||||
* @param dataScope - Atom scope
|
||||
* @param path - Document path
|
||||
* @param pathSegments - Additional path segments appended to the path
|
||||
* @param onError - Called when an error occurs. Make sure to wrap in useCallback!
|
||||
*/
|
||||
export default function useFirestoreDocWithAtom(
|
||||
dataAtom: PrimitiveAtom<DocumentData>,
|
||||
dataScope: Scope | undefined,
|
||||
path: string | undefined,
|
||||
pathSegments?: Array<string | undefined>,
|
||||
onError?: (error: FirestoreError) => void
|
||||
) {
|
||||
const [firebaseDb] = useAtom(firebaseDbAtom, globalScope);
|
||||
const setDataAtom = useUpdateAtom(dataAtom, dataScope);
|
||||
|
||||
useEffect(() => {
|
||||
if (!path || (Array.isArray(pathSegments) && pathSegments.some((x) => !x)))
|
||||
return;
|
||||
|
||||
// Suspend data atom until we get the first snapshot
|
||||
setDataAtom(new Promise(() => {}));
|
||||
|
||||
const unsubscribe = onSnapshot(
|
||||
doc(firebaseDb, path, ...((pathSegments as string[]) || [])),
|
||||
(doc) => {
|
||||
setDataAtom(doc.data()!);
|
||||
},
|
||||
onError
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [firebaseDb, path, pathSegments, onError, setDataAtom]);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
|
||||
import App from "./App";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
77
src/pages/Auth.tsx
Normal file
77
src/pages/Auth.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useAtom } from "jotai";
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
import { currentUserAtom, userRolesAtom } from "@src/atoms/auth";
|
||||
import { publicSettingsAtom } from "@src/atoms/project";
|
||||
|
||||
// import StyledFirebaseAuth from "react-firebaseui/FirebaseAuth";
|
||||
// import "firebase/compat/auth";
|
||||
// import { GoogleAuthProvider } from "firebase/auth";
|
||||
import { firebaseAuthAtom } from "@src/sources/ProjectSourceFirebase";
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
import {
|
||||
GoogleAuthProvider,
|
||||
signInWithPopup,
|
||||
signOut,
|
||||
User,
|
||||
} from "firebase/auth";
|
||||
import { userSettingsAtom } from "@src/atoms/user";
|
||||
const provider = new GoogleAuthProvider();
|
||||
|
||||
function CurrentUser({ currentUser }: { currentUser: User }) {
|
||||
console.log("currentUser", currentUser);
|
||||
return <p>{currentUser?.email}</p>;
|
||||
}
|
||||
|
||||
function Auth() {
|
||||
const [firebaseAuth] = useAtom(firebaseAuthAtom, globalScope);
|
||||
const [currentUser] = useAtom(currentUserAtom, globalScope);
|
||||
const [userRoles] = useAtom(userRolesAtom, globalScope);
|
||||
const [publicSettings] = useAtom(publicSettingsAtom, globalScope);
|
||||
const [userSettings] = useAtom(userSettingsAtom, globalScope);
|
||||
console.log("publicSettings", publicSettings);
|
||||
console.log("userSettings", userSettings);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={currentUser ? "outlined" : "contained"}
|
||||
color={currentUser ? "secondary" : "primary"}
|
||||
onClick={() => {
|
||||
signInWithPopup(firebaseAuth, provider);
|
||||
}}
|
||||
sx={{ my: 4, mx: 1 }}
|
||||
>
|
||||
Sign in with Google
|
||||
</Button>
|
||||
<Button
|
||||
variant={!currentUser ? "outlined" : "contained"}
|
||||
color={!currentUser ? "secondary" : "primary"}
|
||||
onClick={() => {
|
||||
signOut(firebaseAuth);
|
||||
}}
|
||||
sx={{ my: 4, mx: 1 }}
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
|
||||
{currentUser === undefined && <p>Authenticating …</p>}
|
||||
<CurrentUser currentUser={currentUser!} />
|
||||
<p>{JSON.stringify(userRoles)}</p>
|
||||
|
||||
<p>{JSON.stringify(publicSettings)}</p>
|
||||
|
||||
<p>{JSON.stringify(userSettings)}</p>
|
||||
{/* <StyledFirebaseAuth
|
||||
uiConfig={{
|
||||
signInFlow: "popup",
|
||||
signInSuccessUrl: "/",
|
||||
signInOptions: [GoogleAuthProvider.PROVIDER_ID],
|
||||
}}
|
||||
firebaseAuth={firebaseAuth}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
93
src/sources/ProjectSourceFirebase.tsx
Normal file
93
src/sources/ProjectSourceFirebase.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { useEffect } from "react";
|
||||
import { atom, useAtom } from "jotai";
|
||||
|
||||
import { FirebaseOptions, initializeApp } from "firebase/app";
|
||||
import { getAuth, connectAuthEmulator, getIdTokenResult } from "firebase/auth";
|
||||
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
|
||||
|
||||
import { currentUserAtom, userRolesAtom } from "@src/atoms/auth";
|
||||
|
||||
import useFirestoreDocWithAtom from "@src/hooks/useFirestoreDocWithAtom";
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
import { publicSettingsAtom } from "@src/atoms/project";
|
||||
import { useUpdateAtom } from "jotai/utils";
|
||||
import { userSettingsAtom } from "@src/atoms/user";
|
||||
|
||||
const envConfig = {
|
||||
apiKey: process.env.REACT_APP_FIREBASE_PROJECT_WEB_API_KEY,
|
||||
authDomain: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseapp.com`,
|
||||
databaseURL: `https://${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseio.com`,
|
||||
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||
storageBucket: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.appspot.com`,
|
||||
};
|
||||
|
||||
// Connect emulators based on env vars
|
||||
const envConnectEmulators =
|
||||
process.env.NODE_ENV === "test" ||
|
||||
process.env.REACT_APP_FIREBASE_EMULATOR === "true";
|
||||
|
||||
// Store Firebase config here so it can be set programmatically.
|
||||
// This lets us switch between Firebase projects.
|
||||
// Then app, auth, db, storage need to be derived atoms.
|
||||
export const firebaseConfigAtom = atom<FirebaseOptions>(envConfig);
|
||||
|
||||
export const firebaseAppAtom = atom((get) =>
|
||||
initializeApp(get(firebaseConfigAtom))
|
||||
);
|
||||
|
||||
export const firebaseAuthAtom = atom((get) => {
|
||||
const auth = getAuth(get(firebaseAppAtom));
|
||||
if (envConnectEmulators) connectAuthEmulator(auth, "http://localhost:9099");
|
||||
return auth;
|
||||
});
|
||||
|
||||
export const firebaseDbAtom = atom((get) => {
|
||||
const db = getFirestore(get(firebaseAppAtom));
|
||||
if (envConnectEmulators) connectFirestoreEmulator(db, "localhost", 8080);
|
||||
return db;
|
||||
});
|
||||
|
||||
export default function ProjectSourceFirebase() {
|
||||
// Get current user and store in atoms
|
||||
const [firebaseAuth] = useAtom(firebaseAuthAtom, globalScope);
|
||||
// const setCurrentUser: any = useUpdateAtom(currentUserAtom, globalScope);
|
||||
const [currentUser, setCurrentUser] = useAtom(currentUserAtom, globalScope);
|
||||
const setUserRoles = useUpdateAtom(userRolesAtom, globalScope);
|
||||
|
||||
useEffect(() => {
|
||||
// Suspend when currentUser has not been read yet
|
||||
(setCurrentUser as any)(new Promise(() => {}));
|
||||
|
||||
const unsubscribe = firebaseAuth.onAuthStateChanged(async (user) => {
|
||||
setCurrentUser(user);
|
||||
|
||||
if (user) {
|
||||
const tokenResult = await getIdTokenResult(user);
|
||||
setUserRoles((tokenResult.claims.roles as string[]) ?? []);
|
||||
} else {
|
||||
setUserRoles([]);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [firebaseAuth, setCurrentUser, setUserRoles]);
|
||||
|
||||
// Store public settings in atom
|
||||
useFirestoreDocWithAtom(
|
||||
publicSettingsAtom,
|
||||
globalScope,
|
||||
"_rowy_/publicSettings"
|
||||
);
|
||||
|
||||
// Store user settings in atom when a user is signed in
|
||||
useFirestoreDocWithAtom(
|
||||
userSettingsAtom,
|
||||
globalScope,
|
||||
`_rowy_/userManagement/users`,
|
||||
[currentUser?.uid]
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
35
src/sources/RowyProject.tsx
Normal file
35
src/sources/RowyProject.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useState } from "react";
|
||||
import { useUpdateAtom } from "jotai/utils";
|
||||
import { firebaseConfigAtom } from "@src/sources/ProjectSourceFirebase";
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
|
||||
const envConfig = {
|
||||
apiKey: process.env.REACT_APP_FIREBASE_PROJECT_WEB_API_KEY,
|
||||
authDomain: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseapp.com`,
|
||||
databaseURL: `https://${process.env.REACT_APP_FIREBASE_PROJECT_ID}.firebaseio.com`,
|
||||
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||
storageBucket: `${process.env.REACT_APP_FIREBASE_PROJECT_ID}.appspot.com`,
|
||||
appId: "x",
|
||||
};
|
||||
|
||||
export default function RowyProject({ children }: React.PropsWithChildren<{}>) {
|
||||
const [hasConfig, setHasConfig] = useState(false);
|
||||
const setConfigAtom = useUpdateAtom(firebaseConfigAtom, globalScope);
|
||||
|
||||
if (!hasConfig) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setConfigAtom(envConfig);
|
||||
setHasConfig(true);
|
||||
}}
|
||||
>
|
||||
Load Firebase project
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
84
src/theme/CheckboxIcon.tsx
Normal file
84
src/theme/CheckboxIcon.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { toRem } from "./typography";
|
||||
|
||||
export default function CheckboxIcon() {
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: toRem(18),
|
||||
height: toRem(18),
|
||||
margin: toRem((24 - 18) / 2),
|
||||
borderRadius: 1,
|
||||
display: "flex",
|
||||
|
||||
position: "relative",
|
||||
|
||||
bgcolor: "action.input",
|
||||
border: "1px solid",
|
||||
borderColor: "text.disabled",
|
||||
color: "primary.main",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
delay: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"& svg": {
|
||||
position: "absolute",
|
||||
width: toRem(18),
|
||||
height: toRem(18),
|
||||
top: -1,
|
||||
left: -1,
|
||||
color: "inherit",
|
||||
},
|
||||
"& .tick": {
|
||||
stroke: (theme) => theme.palette.primary.contrastText,
|
||||
strokeDasharray: 18,
|
||||
strokeDashoffset: 18,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["stroke-dashoffset"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
boxShadow: 1,
|
||||
},
|
||||
|
||||
".Mui-checked &, [aria-selected='true'] &": {
|
||||
backgroundColor: "currentColor",
|
||||
borderColor: "currentColor",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"& .tick": {
|
||||
strokeDashoffset: 0,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["stroke-dashoffset"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
delay: theme.transitions.duration.shortest,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 18 18">
|
||||
<polyline
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
points="2.705 8.29 7 12.585 15.295 4.29"
|
||||
fill="none"
|
||||
className="tick"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
85
src/theme/CheckboxIndeterminateIcon.tsx
Normal file
85
src/theme/CheckboxIndeterminateIcon.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { toRem } from "./typography";
|
||||
|
||||
export default function CheckboxIndeterminateIcon() {
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: toRem(18),
|
||||
height: toRem(18),
|
||||
margin: toRem((24 - 18) / 2),
|
||||
borderRadius: 1,
|
||||
display: "flex",
|
||||
|
||||
position: "relative",
|
||||
|
||||
bgcolor: "action.input",
|
||||
border: "1px solid",
|
||||
borderColor: "text.disabled",
|
||||
color: "primary.main",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
delay: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"& svg": {
|
||||
position: "absolute",
|
||||
width: toRem(18),
|
||||
height: toRem(18),
|
||||
top: -1,
|
||||
left: -1,
|
||||
color: "inherit",
|
||||
},
|
||||
"& .tick": {
|
||||
stroke: (theme) => theme.palette.primary.contrastText,
|
||||
strokeDasharray: 12,
|
||||
strokeDashoffset: 12,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["stroke-dashoffset"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
boxShadow: 1,
|
||||
},
|
||||
|
||||
".Mui-checked &, [aria-selected='true'] &": {
|
||||
backgroundColor: "currentColor",
|
||||
borderColor: "currentColor",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"& .tick": {
|
||||
strokeDashoffset: 0,
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["stroke-dashoffset"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
delay: theme.transitions.duration.shortest,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 18 18">
|
||||
<line
|
||||
x1="3"
|
||||
y1="9"
|
||||
x2="15"
|
||||
y2="9"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
className="tick"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
66
src/theme/RadioIcon.tsx
Normal file
66
src/theme/RadioIcon.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { toRem } from "./typography";
|
||||
|
||||
export default function RadioIcon() {
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: toRem(20),
|
||||
height: toRem(20),
|
||||
margin: toRem((24 - 20) / 2),
|
||||
borderRadius: "50%",
|
||||
display: "flex",
|
||||
|
||||
backgroundColor: "transparent",
|
||||
border: "1px solid",
|
||||
borderColor: "text.disabled",
|
||||
color: "primary.main",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"&::before": {
|
||||
content: '""',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: "50%",
|
||||
display: "block",
|
||||
|
||||
bgcolor: "action.input",
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["transform", "background-color"], {
|
||||
easing: theme.transitions.easing.easeIn,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
},
|
||||
|
||||
".Mui-checked &, [aria-selected='true'] &": {
|
||||
backgroundColor: "currentColor",
|
||||
borderColor: "currentColor",
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["background-color", "border-color"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
|
||||
"&::before": {
|
||||
bgcolor: "primary.contrastText",
|
||||
boxShadow: 1,
|
||||
transform: `scale(${12 / 20})`,
|
||||
|
||||
transition: (theme) =>
|
||||
theme.transitions.create(["transform", "background-color"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
60
src/theme/ThemeProvider.tsx
Normal file
60
src/theme/ThemeProvider.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import {
|
||||
useMediaQuery,
|
||||
ThemeProvider as MuiThemeProvider,
|
||||
CssBaseline,
|
||||
} from "@mui/material";
|
||||
|
||||
import { globalScope } from "@src/atoms/globalScope";
|
||||
import {
|
||||
themeAtom,
|
||||
themeOverriddenAtom,
|
||||
customizedThemesAtom,
|
||||
} from "@src/atoms/user";
|
||||
|
||||
/**
|
||||
* Injects the MUI theme with customizations from project and user settings.
|
||||
* Also adds dark mode support.
|
||||
*/
|
||||
export default function ThemeProvider({
|
||||
children,
|
||||
}: React.PropsWithChildren<{}>) {
|
||||
const [theme, setTheme] = useAtom(themeAtom, globalScope);
|
||||
const [themeOverridden] = useAtom(themeOverriddenAtom, globalScope);
|
||||
const [customizedThemes] = useAtom(customizedThemesAtom, globalScope);
|
||||
|
||||
// Infer theme based on system settings
|
||||
const prefersDarkTheme = useMediaQuery("(prefers-color-scheme: dark)", {
|
||||
noSsr: true,
|
||||
});
|
||||
|
||||
// Update theme when system settings change
|
||||
useEffect(() => {
|
||||
if (themeOverridden) return;
|
||||
setTheme(prefersDarkTheme ? "dark" : "light");
|
||||
}, [prefersDarkTheme, themeOverridden, setTheme]);
|
||||
|
||||
// Sync theme to body data-theme attribute for Feedback Fin
|
||||
useEffect(() => {
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{Array.isArray(customizedThemes[theme].typography.fontCssUrls) && (
|
||||
<Helmet>
|
||||
{customizedThemes[theme].typography.fontCssUrls!.map((url) => (
|
||||
<link key={url} rel="stylesheet" href={url} />
|
||||
))}
|
||||
</Helmet>
|
||||
)}
|
||||
|
||||
<MuiThemeProvider theme={customizedThemes[theme]}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</MuiThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
209
src/theme/colors.ts
Normal file
209
src/theme/colors.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { ThemeOptions } from "@mui/material/styles";
|
||||
import { Shadows } from "@mui/material/styles/shadows";
|
||||
|
||||
import { colord, extend } from "colord";
|
||||
import lchPlugin from "colord/plugins/lch";
|
||||
extend([lchPlugin]);
|
||||
|
||||
declare module "@mui/material/styles/createPalette" {
|
||||
interface TypeAction {
|
||||
activeOpacity: number;
|
||||
input: string;
|
||||
inputOutline: string;
|
||||
}
|
||||
}
|
||||
|
||||
export const PRIMARY = "#4200FF";
|
||||
export const ERROR = "#B00020"; // https://material.io/design/color/dark-theme.html#ui-application
|
||||
export const DARK_PRIMARY = "#B0B6FD"; // l: 75, c: 65, h: 275
|
||||
|
||||
export const colorsLight = (
|
||||
_primary: Parameters<typeof colord>[0] = PRIMARY
|
||||
): ThemeOptions => {
|
||||
const primary = colord(_primary);
|
||||
const h = primary.toLch().h;
|
||||
const secondary = colord({ l: 10, c: 10, h });
|
||||
const secondaryDark = colord({ l: 0, c: 0, h });
|
||||
const bgDefault = colord({ l: 98, c: 1, h });
|
||||
const textBase = colord({ l: 0, c: 10, h });
|
||||
const shadowBase = colord({ l: 0, c: 10, h });
|
||||
const tooltip = shadowBase.alpha(0.8);
|
||||
|
||||
return {
|
||||
palette: {
|
||||
primary: { main: primary.toHslString() },
|
||||
secondary: {
|
||||
main: secondary.toHslString(),
|
||||
dark: secondaryDark.toHslString(),
|
||||
},
|
||||
error: { main: ERROR },
|
||||
success: { light: "#34c759", main: "#00802e", dark: "#105e24" },
|
||||
background: { default: bgDefault.toHslString() },
|
||||
text: {
|
||||
primary: textBase.alpha(0.87).toHslString(),
|
||||
secondary: textBase.alpha(0.6).toHslString(),
|
||||
disabled: textBase.alpha(0.38).toHslString(),
|
||||
},
|
||||
action: {
|
||||
active: textBase.alpha(0.6).toHslString(),
|
||||
activeOpacity: 0.6,
|
||||
hover: textBase.alpha(0.04).toHslString(),
|
||||
selected: textBase.alpha(0.08).toHslString(),
|
||||
disabled: textBase.alpha(0.26).toHslString(),
|
||||
disabledBackground: textBase.alpha(0.12).toHslString(),
|
||||
input: "#fff",
|
||||
inputOutline: shadowBase.alpha(0.12).toRgbString(),
|
||||
},
|
||||
divider: shadowBase.alpha(0.12).toRgbString(), // Using hsl string breaks table borders
|
||||
},
|
||||
shadows: new Array(25).fill(undefined).map((_, i) => {
|
||||
// Based on https://tailwindcss.com/docs/box-shadow
|
||||
// with additional “outline” shadow
|
||||
// and bigger shadow from https://github.com/outline/outline/blob/37fd7ec97a496094077a59f4d10fa0081516e3ef/shared/theme.js#L148
|
||||
|
||||
if (i === 0) return "none";
|
||||
|
||||
if (i === 1)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.03).toHslString()}, 0 1px 2px 0 ${shadowBase.alpha(0.1).toHslString()}`;
|
||||
|
||||
if (i < 4)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.04).toHslString()}, 0 1px 3px 0 ${shadowBase.alpha(0.1).toHslString()}, 0 1px 2px 0 ${shadowBase.alpha(0.06).toHslString()}`;
|
||||
|
||||
if (i < 8)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.05).toHslString()}, 0 4px 6px -1px ${shadowBase.alpha(0.1).toHslString()}, 0 2px 4px ${shadowBase.alpha(0.06).toHslString()}`;
|
||||
|
||||
if (i < 16)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.06).toHslString()}, 0 10px 15px -3px ${shadowBase.alpha(0.1).toHslString()}, 0 4px 6px ${shadowBase.alpha(0.05).toHslString()}, 0 30px 40px ${shadowBase.alpha(0.05).toHslString()}`;
|
||||
|
||||
if (i < 24)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.08).toHslString()}, 0 20px 25px -5px ${shadowBase.alpha(0.1).toHslString()}, 0 10px 10px ${shadowBase.alpha(0.04).toHslString()}, 0 40px 60px ${shadowBase.alpha(0.06).toHslString()}`;
|
||||
|
||||
else
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.08).toHslString()}, 0 25px 50px -12px ${shadowBase.alpha(0.25).toHslString()}, 0 50px 80px ${shadowBase.alpha(0.06).toHslString()}`;
|
||||
}) as Shadows,
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
":root": { colorScheme: "light" },
|
||||
".rdg": { colorScheme: "light" },
|
||||
},
|
||||
},
|
||||
MuiBackdrop: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: colord({ l: 70, c: 5, h })
|
||||
.alpha(0.6)
|
||||
.toHslString(),
|
||||
},
|
||||
invisible: { backgroundColor: "transparent" },
|
||||
},
|
||||
},
|
||||
MuiTooltip: {
|
||||
styleOverrides: {
|
||||
tooltip: { backgroundColor: tooltip.toHslString() },
|
||||
arrow: { color: tooltip.toHslString() },
|
||||
},
|
||||
},
|
||||
MuiSlider: {
|
||||
styleOverrides: {
|
||||
valueLabel: { backgroundColor: secondary.toHslString() },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const colorsDark = (
|
||||
_primary: Parameters<typeof colord>[0] = DARK_PRIMARY,
|
||||
darker?: boolean
|
||||
): ThemeOptions => {
|
||||
const primary = colord(_primary);
|
||||
const h = primary.toLch().h;
|
||||
const secondary = colord({ l: 96, c: 1, h });
|
||||
const bgDefault = darker ? colord("#000") : colord({ l: 5, c: 2, h });
|
||||
const bgPaper = darker ? colord("#000") : bgDefault;
|
||||
const shadowBase = colord({ l: 0, c: 2, h });
|
||||
|
||||
return {
|
||||
palette: {
|
||||
mode: "dark",
|
||||
primary: { main: primary.toHslString() },
|
||||
secondary: { main: secondary.toHslString() },
|
||||
background: {
|
||||
default: bgDefault.toHslString(),
|
||||
paper: bgPaper.toHslString(),
|
||||
},
|
||||
error: {
|
||||
main: colord({
|
||||
l: 75,
|
||||
c: 72,
|
||||
h: colord(ERROR).toLch().h,
|
||||
}).toHslString(),
|
||||
},
|
||||
action: {
|
||||
active: "rgba(255, 255, 255, 0.7)",
|
||||
activeOpacity: 0.7,
|
||||
hover: "rgba(255, 255, 255, 0.08)",
|
||||
hoverOpacity: 0.08,
|
||||
input: "rgba(255, 255, 255, 0.06)",
|
||||
inputOutline: "rgba(255, 255, 255, 0.08)",
|
||||
},
|
||||
// success: { light: "#34c759" },
|
||||
},
|
||||
shadows: new Array(25).fill(undefined).map((_, i) => {
|
||||
// Based on https://tailwindcss.com/docs/box-shadow
|
||||
// with additional “outline” shadow
|
||||
// and bigger shadow from https://github.com/outline/outline/blob/37fd7ec97a496094077a59f4d10fa0081516e3ef/shared/theme.js#L148
|
||||
|
||||
if (i === 0) return "none";
|
||||
|
||||
if (i === 1)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.03 * 4).toHslString()}, 0 1px 2px 0 ${shadowBase.alpha(0.1 * 4).toHslString()}`;
|
||||
|
||||
if (i < 4)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.04 * 4).toHslString()}, 0 1px 3px 0 ${shadowBase.alpha(0.1 * 4).toHslString()}, 0 1px 2px 0 ${shadowBase.alpha(0.06 * 4).toHslString()}`;
|
||||
|
||||
if (i < 8)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.05 * 4).toHslString()}, 0 4px 6px -1px ${shadowBase.alpha(0.1 * 4).toHslString()}, 0 2px 4px ${shadowBase.alpha(0.06 * 4).toHslString()}`;
|
||||
|
||||
if (i < 16)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.06 * 4).toHslString()}, 0 10px 15px -3px ${shadowBase.alpha(0.1 * 4).toHslString()}, 0 4px 6px ${shadowBase.alpha(0.05 * 4).toHslString()}, 0 30px 40px ${shadowBase.alpha(0.05 * 4).toHslString()}`;
|
||||
|
||||
if (i < 24)
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.08 * 4).toHslString()}, 0 20px 25px -5px ${shadowBase.alpha(0.1 * 4).toHslString()}, 0 10px 10px ${shadowBase.alpha(0.04 * 4).toHslString()}, 0 40px 60px ${shadowBase.alpha(0.06 * 4).toHslString()}`;
|
||||
|
||||
else
|
||||
// prettier-ignore
|
||||
return `0 0 0 1px ${shadowBase.alpha(0.08 * 4).toHslString()}, 0 25px 50px -12px ${shadowBase.alpha(0.25 * 4).toHslString()}, 0 50px 80px ${shadowBase.alpha(0.06 * 4).toHslString()}`;
|
||||
}) as Shadows,
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
":root": { colorScheme: "dark" },
|
||||
".rdg": { colorScheme: "dark" },
|
||||
},
|
||||
},
|
||||
MuiBackdrop: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: colord({ l: 0, c: 1, h }).alpha(0.6).toHslString(),
|
||||
},
|
||||
invisible: { backgroundColor: "transparent" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
1211
src/theme/components.tsx
Normal file
1211
src/theme/components.tsx
Normal file
File diff suppressed because it is too large
Load Diff
53
src/theme/index.tsx
Normal file
53
src/theme/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { createTheme, ThemeOptions } from "@mui/material/styles";
|
||||
import _merge from "lodash/merge";
|
||||
|
||||
import { typography } from "@src/theme/typography";
|
||||
import { colorsLight, colorsDark } from "@src/theme/colors";
|
||||
import { components } from "@src/theme/components";
|
||||
|
||||
export const customizableLightTheme = (customization: ThemeOptions) => {
|
||||
const customizedLightThemeBase = createTheme(
|
||||
_merge(
|
||||
{},
|
||||
typography((customization?.typography as any) ?? {}),
|
||||
colorsLight((customization?.palette?.primary as any)?.main)
|
||||
)
|
||||
);
|
||||
|
||||
return createTheme(
|
||||
_merge(
|
||||
{},
|
||||
customizedLightThemeBase,
|
||||
components(customizedLightThemeBase),
|
||||
customization
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const customizableDarkTheme = (customization: ThemeOptions) => {
|
||||
const customizedDarkThemeBase = createTheme(
|
||||
_merge(
|
||||
{},
|
||||
typography((customization?.typography as any) ?? {}),
|
||||
colorsDark(
|
||||
(customization?.palette?.primary as any)?.main,
|
||||
(customization?.palette as any)?.darker
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return createTheme(
|
||||
_merge(
|
||||
{},
|
||||
customizedDarkThemeBase,
|
||||
components(customizedDarkThemeBase),
|
||||
customization
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const themes = {
|
||||
light: customizableLightTheme,
|
||||
dark: customizableDarkTheme,
|
||||
};
|
||||
export default themes;
|
||||
148
src/theme/palette.ts
Normal file
148
src/theme/palette.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
export const palette = {
|
||||
aBlack: {
|
||||
500: "#282829",
|
||||
},
|
||||
aWhite: {
|
||||
500: "#ffffff",
|
||||
},
|
||||
aRed: {
|
||||
100: "#fae4e5",
|
||||
300: "#fb8c8c",
|
||||
500: "#ed4747",
|
||||
600: "#e91c1c",
|
||||
700: "#c12929",
|
||||
},
|
||||
aGray: {
|
||||
50: "#fafafa",
|
||||
100: "#f2f2f2",
|
||||
200: "#e9e9e9",
|
||||
300: "#cccccc",
|
||||
500: "#999999",
|
||||
700: "#595959",
|
||||
},
|
||||
|
||||
gray: {
|
||||
100: "#f2f2f2",
|
||||
300: "#cccccc",
|
||||
500: "#999999",
|
||||
700: "#595959",
|
||||
},
|
||||
blueGray: {
|
||||
100: "#93a4ad",
|
||||
300: "#647c8a",
|
||||
500: "#485a63",
|
||||
700: "#394c55",
|
||||
},
|
||||
indigo: {
|
||||
100: "#7986cb",
|
||||
300: "#3f51b5",
|
||||
500: "#303f9f",
|
||||
700: "#213092",
|
||||
},
|
||||
blue: {
|
||||
100: "#64b5f6",
|
||||
300: "#2196f3",
|
||||
500: "#1976d2",
|
||||
700: "#0a59a8",
|
||||
},
|
||||
lightBlue: {
|
||||
100: "#4fc3f7",
|
||||
300: "#03a9f4",
|
||||
500: "#0b8ed6",
|
||||
700: "#007fc5",
|
||||
},
|
||||
cyan: {
|
||||
100: "#4dd0e1",
|
||||
300: "#00bcd4",
|
||||
500: "#0097a7",
|
||||
700: "#258493",
|
||||
},
|
||||
teal: {
|
||||
100: "#4db6ac",
|
||||
300: "#049587",
|
||||
500: "#037b6d",
|
||||
700: "#006055",
|
||||
},
|
||||
green: {
|
||||
100: "#81c784",
|
||||
300: "#4caf50",
|
||||
500: "#388e3c",
|
||||
700: "#2e7d32",
|
||||
},
|
||||
lightGreen: {
|
||||
100: "#aed581",
|
||||
300: "#8bc34a",
|
||||
500: "#689f38",
|
||||
700: "#4f6f33",
|
||||
},
|
||||
lime: {
|
||||
100: "#dce775",
|
||||
300: "#cddc39",
|
||||
500: "#afb42b",
|
||||
700: "#8e9d01",
|
||||
},
|
||||
yellow: {
|
||||
100: "#fff176",
|
||||
300: "#ffeb3b",
|
||||
500: "#fbc02d",
|
||||
700: "#d07e04",
|
||||
},
|
||||
amber: {
|
||||
100: "#ffd54f",
|
||||
300: "#ffc107",
|
||||
500: "#ffa000",
|
||||
700: "#b38c2b",
|
||||
},
|
||||
orange: {
|
||||
100: "#ffb74d",
|
||||
300: "#ff9800",
|
||||
500: "#f57c00",
|
||||
700: "#bb661e",
|
||||
},
|
||||
brown: {
|
||||
100: "#a1887f",
|
||||
300: "#795548",
|
||||
500: "#5d4037",
|
||||
700: "#4e3229",
|
||||
},
|
||||
tangerine: {
|
||||
100: "#ff8a65",
|
||||
300: "#ff5722",
|
||||
500: "#e64a19",
|
||||
700: "#c0360a",
|
||||
},
|
||||
errorRed: {
|
||||
100: "#e57373",
|
||||
300: "#f44336",
|
||||
500: "#d32f2f",
|
||||
700: "#c62323",
|
||||
},
|
||||
pink: {
|
||||
100: "#f06292",
|
||||
300: "#e91e63",
|
||||
500: "#c2185b",
|
||||
700: "#b0104f",
|
||||
},
|
||||
purple: {
|
||||
100: "#ba68c8",
|
||||
300: "#9c27b0",
|
||||
500: "#7b1fa2",
|
||||
700: "#650e89",
|
||||
},
|
||||
violet: {
|
||||
100: "#9575cd",
|
||||
300: "#673ab7",
|
||||
500: "#512da8",
|
||||
700: "#341878",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paletteToMui = (
|
||||
color: Record<"700" | "300" | "500" | "100", string>
|
||||
) => ({
|
||||
main: color[500],
|
||||
light: color[300],
|
||||
dark: color[700],
|
||||
});
|
||||
|
||||
export default palette;
|
||||
169
src/theme/typography.ts
Normal file
169
src/theme/typography.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { ThemeOptions } from "@mui/material/styles";
|
||||
import {
|
||||
FontStyle,
|
||||
TypographyStyleOptions,
|
||||
} from "@mui/material/styles/createTypography";
|
||||
|
||||
declare module "@mui/material/styles/createTypography" {
|
||||
interface FontStyle {
|
||||
fontFamilyMono: string;
|
||||
fontFamilyHeading: string;
|
||||
fontCssUrls?: string[];
|
||||
}
|
||||
}
|
||||
|
||||
export const BODY_FONT = "Inter, system-ui, sans-serif";
|
||||
export const HEADING_FONT = "Space Grotesk, " + BODY_FONT;
|
||||
export const MONO_FONT = "JetBrains Mono, ui-monospace, monospace";
|
||||
|
||||
export const ROOT_FONT_SIZE = 16;
|
||||
export const toRem = (px: number) => `${px / ROOT_FONT_SIZE}rem`;
|
||||
export const toEm = (px: number, root: number) => `${px / root}em`;
|
||||
|
||||
export const typography = ({
|
||||
fontFamily: customizedFontFamily,
|
||||
fontFamilyMono: customizedFontFamilyMono,
|
||||
fontFamilyHeading: customizedFontFamilyHeading,
|
||||
|
||||
fontWeightLight = 300,
|
||||
fontWeightRegular = 400,
|
||||
fontWeightMedium = 500,
|
||||
fontWeightBold = 600,
|
||||
}: Partial<ThemeOptions["typography"] & FontStyle>): ThemeOptions => {
|
||||
const fontFamily = [customizedFontFamily, BODY_FONT]
|
||||
.filter((x) => x)
|
||||
.join(", ");
|
||||
const fontFamilyMono = [customizedFontFamilyMono, MONO_FONT]
|
||||
.filter((x) => x)
|
||||
.join(", ");
|
||||
const fontFamilyHeading = [customizedFontFamilyHeading, HEADING_FONT]
|
||||
.filter((x) => x)
|
||||
.join(", ");
|
||||
|
||||
const fontStyleBody: TypographyStyleOptions = {
|
||||
fontFamily,
|
||||
fontFeatureSettings:
|
||||
fontFamily !== BODY_FONT
|
||||
? "normal"
|
||||
: `"calt", "ss01", "ss03", "cv05", "cv08", "cv09"`,
|
||||
};
|
||||
const fontStyleHeading: TypographyStyleOptions = {
|
||||
fontFamily: fontFamilyHeading,
|
||||
fontFeatureSettings:
|
||||
fontFamilyHeading !== HEADING_FONT ? "normal" : `"ss02", "ss03"`,
|
||||
};
|
||||
|
||||
return {
|
||||
typography: {
|
||||
fontFamily,
|
||||
fontFamilyMono,
|
||||
fontFamilyHeading,
|
||||
|
||||
fontWeightLight,
|
||||
fontWeightRegular,
|
||||
fontWeightMedium,
|
||||
fontWeightBold,
|
||||
|
||||
h1: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(96),
|
||||
letterSpacing: toEm(-1.5, 96),
|
||||
lineHeight: 112 / 96,
|
||||
},
|
||||
h2: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(60),
|
||||
letterSpacing: toEm(-0.75, 60),
|
||||
lineHeight: 72 / 60,
|
||||
},
|
||||
h3: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(48),
|
||||
letterSpacing: toEm(-0.5, 48),
|
||||
lineHeight: 60 / 48,
|
||||
},
|
||||
h4: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(34),
|
||||
letterSpacing: toEm(-0.35, 34),
|
||||
lineHeight: 44 / 34,
|
||||
},
|
||||
h5: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(24),
|
||||
letterSpacing: "0",
|
||||
// letterSpacing: toEm(-0.2, 24),
|
||||
lineHeight: 32 / 24,
|
||||
},
|
||||
h6: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(20),
|
||||
letterSpacing: "0",
|
||||
// letterSpacing: toEm(-0.15, 20),
|
||||
lineHeight: 28 / 20,
|
||||
},
|
||||
subtitle1: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(17),
|
||||
letterSpacing: toEm(0.2, 16),
|
||||
lineHeight: 24 / 17,
|
||||
},
|
||||
subtitle2: {
|
||||
...fontStyleHeading,
|
||||
fontWeight: fontWeightBold,
|
||||
fontSize: toRem(15),
|
||||
letterSpacing: toEm(0.25, 14),
|
||||
lineHeight: 20 / 15,
|
||||
},
|
||||
body1: {
|
||||
...fontStyleBody,
|
||||
fontSize: toRem(16),
|
||||
letterSpacing: toEm(0.5, 16),
|
||||
lineHeight: 24 / 16,
|
||||
},
|
||||
body2: {
|
||||
...fontStyleBody,
|
||||
fontSize: toRem(14),
|
||||
letterSpacing: toEm(0.25, 14),
|
||||
lineHeight: 20 / 14,
|
||||
},
|
||||
button: {
|
||||
...fontStyleBody,
|
||||
fontWeight: fontWeightMedium,
|
||||
fontSize: toRem(14),
|
||||
letterSpacing: toEm(0.25, 14),
|
||||
lineHeight: 20 / 14,
|
||||
textTransform: "none",
|
||||
},
|
||||
caption: {
|
||||
...fontStyleBody,
|
||||
fontSize: toRem(12),
|
||||
letterSpacing: toEm(0.4, 12),
|
||||
lineHeight: 20 / 12,
|
||||
},
|
||||
overline: {
|
||||
...fontStyleBody,
|
||||
fontSize: toRem(12),
|
||||
letterSpacing: toEm(1.5, 12),
|
||||
lineHeight: 20 / 12,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiTypography: {
|
||||
defaultProps: { variant: "body2" },
|
||||
},
|
||||
MuiLink: {
|
||||
styleOverrides: {
|
||||
root: { textUnderlineOffset: toRem(1) },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -17,8 +17,8 @@
|
||||
"newLine": "lf",
|
||||
"noEmit": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "src"
|
||||
},
|
||||
|
||||
269
yarn.lock
269
yarn.lock
@@ -2702,6 +2702,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@mdi/js/-/js-6.6.96.tgz#119f79fa9327359421167a7d4b8bde26e84702ce"
|
||||
integrity sha512-ke9PN5DjPCOlMfhioxeZYADz8Yiz6v47W0IYRza01SSJD7y1EwESVpwFnnFUso+eCoWtE1CO9cTIvQF6sEreuA==
|
||||
|
||||
"@microsoft/tsdoc-config@0.16.1":
|
||||
version "0.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz#4de11976c1202854c4618f364bf499b4be33e657"
|
||||
integrity sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==
|
||||
dependencies:
|
||||
"@microsoft/tsdoc" "0.14.1"
|
||||
ajv "~6.12.6"
|
||||
jju "~1.4.0"
|
||||
resolve "~1.19.0"
|
||||
|
||||
"@microsoft/tsdoc@0.14.1":
|
||||
version "0.14.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd"
|
||||
integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==
|
||||
|
||||
"@mui/base@5.0.0-alpha.75":
|
||||
version "5.0.0-alpha.75"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.75.tgz#0b352ff951765dfa83723fd1faf46a128dfe63ca"
|
||||
@@ -2974,6 +2989,11 @@
|
||||
estree-walker "^1.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@rowy/multiselect@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rowy/multiselect/-/multiselect-0.2.3.tgz#caf5ee769a6c3ce5a4124120d38820caf1f411a1"
|
||||
integrity sha512-FiESN3VE2Rz++y6cZXzkA4RJmRZ0r3/1Hy6oAfJtroJ/H/r0xjJYGA5qwpmQKNI08W0KCfkBC7JM0OtRiD3GVA==
|
||||
|
||||
"@rushstack/eslint-patch@^1.1.0":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.1.tgz#782fa5da44c4f38ae9fd38e9184b54e451936118"
|
||||
@@ -3231,6 +3251,14 @@
|
||||
lodash "^4.17.15"
|
||||
redent "^3.0.0"
|
||||
|
||||
"@testing-library/react-hooks@^8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.0.tgz#7d0164bffce4647f506039de0a97f6fcbd20f4bf"
|
||||
integrity sha512-uZqcgtcUUtw7Z9N32W13qQhVAD+Xki2hxbTR461MKax8T6Jr8nsUvZB+vcBTkzY2nFvsUet434CsgF0ncW2yFw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
react-error-boundary "^3.1.0"
|
||||
|
||||
"@testing-library/react@^13.0.0":
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.0.0.tgz#8cdaf4667c6c2b082eb0513731551e9db784e8bc"
|
||||
@@ -3498,7 +3526,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash@^4.14.181":
|
||||
"@types/lodash-es@^4.17.6":
|
||||
version "4.17.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0"
|
||||
integrity sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*", "@types/lodash@^4.14.181":
|
||||
version "4.14.181"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d"
|
||||
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==
|
||||
@@ -3563,6 +3598,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-div-100vh@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-div-100vh/-/react-div-100vh-0.4.0.tgz#750e3ac45ee239ec2952089c1516f3b510bd103e"
|
||||
integrity sha512-mkgDNAqtgoKnTu58DBKT+/AbVxEN1cqc+pNqTKvwPduxgE9WiAVKs4k1wGJ95oJzWX/apnwGM99A560B6c01xw==
|
||||
dependencies:
|
||||
react-div-100vh "*"
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@^18.0.0":
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.0.tgz#b13f8d098e4b0c45df4f1ed123833143b0c71141"
|
||||
@@ -3601,19 +3643,10 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*":
|
||||
version "17.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991"
|
||||
integrity sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@^18.0.0":
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.0.tgz#4be8aa3a2d04afc3ac2cc1ca43d39b0bd412890c"
|
||||
integrity sha512-7+K7zEQYu7NzOwQGLR91KwWXXDzmTFODRVizJyIALf6RfLv2GDpqpknX64pvRVILXCpXi7O/pua8NGk44dLvJw==
|
||||
"@types/react@*", "@types/react@^18", "@types/react@^18.0.5":
|
||||
version "18.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.5.tgz#1a4d4b705ae6af5aed369dec22800b20f89f5301"
|
||||
integrity sha512-UPxNGInDCIKlfqBrm8LDXYWNfLHwIdisWcsH5GpMyGjhEDLFgTtlRBaoWuCua9HcyuE0rMkmAeZ3FXV1pYLIYQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
@@ -4034,7 +4067,7 @@ ajv-keywords@^5.0.0:
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.3"
|
||||
|
||||
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5:
|
||||
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@~6.12.6:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
@@ -4234,6 +4267,11 @@ async@^2.6.2:
|
||||
dependencies:
|
||||
lodash "^4.17.14"
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
@@ -4538,6 +4576,11 @@ bser@2.1.1:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
btoa@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
|
||||
integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
@@ -4609,15 +4652,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001181:
|
||||
version "1.0.30001191"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9"
|
||||
integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==
|
||||
|
||||
caniuse-lite@^1.0.30001317:
|
||||
version "1.0.30001325"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
|
||||
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001317:
|
||||
version "1.0.30001328"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001328.tgz"
|
||||
integrity sha512-Ue55jHkR/s4r00FLNiX+hGMMuwml/QGqqzVeMQ5thUewznU2EdULFvI3JR7JJid6OrjJNfFvHY2G2dIjmRaDDQ==
|
||||
|
||||
case-sensitive-paths-webpack-plugin@^2.4.0:
|
||||
version "2.4.0"
|
||||
@@ -5576,6 +5614,13 @@ ee-first@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
ejs@^3.1.5:
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006"
|
||||
integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==
|
||||
dependencies:
|
||||
jake "^10.8.5"
|
||||
|
||||
ejs@^3.1.6:
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
|
||||
@@ -5727,7 +5772,7 @@ escalade@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
escape-html@~1.0.3:
|
||||
escape-html@^1.0.3, escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
@@ -5852,6 +5897,11 @@ eslint-plugin-jsx-a11y@^6.5.1:
|
||||
language-tags "^1.0.5"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
eslint-plugin-no-relative-import-paths@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-no-relative-import-paths/-/eslint-plugin-no-relative-import-paths-1.2.0.tgz#ae969752ae910f4a92f7d3f467076e3cb19c6acf"
|
||||
integrity sha512-UQiGNGU8Jite02FLMgB0J1Cg4D9GwpeDPvJo6x1++eKqqghNGRyFA68WVx6p6KjD9THDaenXnDOj/1YAobQ5Ag==
|
||||
|
||||
eslint-plugin-react-hooks@^4.3.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz#71c39e528764c848d8253e1aa2c7024ed505f6c4"
|
||||
@@ -5884,6 +5934,14 @@ eslint-plugin-testing-library@^5.0.1:
|
||||
dependencies:
|
||||
"@typescript-eslint/utils" "^5.13.0"
|
||||
|
||||
eslint-plugin-tsdoc@^0.2.16:
|
||||
version "0.2.16"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz#a3d31fb9c7955faa3c66a43dd43da7635f1c5e0d"
|
||||
integrity sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw==
|
||||
dependencies:
|
||||
"@microsoft/tsdoc" "0.14.1"
|
||||
"@microsoft/tsdoc-config" "0.16.1"
|
||||
|
||||
eslint-scope@5.1.1, eslint-scope@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
@@ -6280,7 +6338,7 @@ firebase@^9.6.10:
|
||||
"@firebase/storage-compat" "0.1.12"
|
||||
"@firebase/util" "1.5.1"
|
||||
|
||||
firebaseui@^6.0.0, firebaseui@^6.0.1:
|
||||
firebaseui@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-6.0.1.tgz#bdabc69de9c245975a1277d507b2f0ff0044e558"
|
||||
integrity sha512-SMNCFt/xns3mnvd0hImEDu7di5fqRU3MVyIaXpVEfg6v0bH6f3m+YybivU7KElRUT/47DHMn++D8MrZYYnoN5g==
|
||||
@@ -6844,6 +6902,13 @@ internal-slot@^1.0.3:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
@@ -6886,6 +6951,13 @@ is-callable@^1.1.4, is-callable@^1.2.2, is-callable@^1.2.4:
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
|
||||
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
|
||||
|
||||
is-core-module@^2.1.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-core-module@^2.2.0, is-core-module@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
|
||||
@@ -7042,7 +7114,7 @@ is-weakref@^1.0.2:
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-wsl@^2.2.0:
|
||||
is-wsl@^2.1.1, is-wsl@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||
@@ -7116,6 +7188,16 @@ jake@^10.6.1:
|
||||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.8.5"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
|
||||
integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
|
||||
dependencies:
|
||||
async "^3.2.3"
|
||||
chalk "^4.0.2"
|
||||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
jest-changed-files@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5"
|
||||
@@ -7558,10 +7640,15 @@ jest@^27.4.3:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^27.5.1"
|
||||
|
||||
jotai@^1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.6.2.tgz#101193b24701ce0621eb017f8dc3d35be3f14da0"
|
||||
integrity sha512-WglyuXX67f8QPCOX5tDR0N9GhasOV1+IQ3piaF4owr2wBn2CV51T8zbw01ZCme/24JKJilFe6+aA29PJx+jsQw==
|
||||
jju@~1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
|
||||
integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo=
|
||||
|
||||
jotai@^1.6.4:
|
||||
version "1.6.4"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.6.4.tgz#4d9904362c53c4293d32e21fb358d3de34b82912"
|
||||
integrity sha512-XC0ExLhdE6FEBdIjKTe6kMlHaAUV/QiwN7vZond76gNr/WdcdonJOEW79+5t8u38sR41bJXi26B1dRi7cCRz9A==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -7935,6 +8022,11 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
@@ -7985,7 +8077,7 @@ long@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@@ -8180,6 +8272,13 @@ minimist@^1.2.0, minimist@^1.2.6:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
mkdirp@^0.5.5, mkdirp@~0.5.1:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
@@ -8431,6 +8530,14 @@ onetime@^5.1.0, onetime@^5.1.2:
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
open@^7.3.1:
|
||||
version "7.4.2"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
|
||||
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
open@^8.0.9, open@^8.4.0:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
|
||||
@@ -9468,6 +9575,11 @@ react-dev-utils@^12.0.0:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
react-div-100vh@*, react-div-100vh@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.7.0.tgz#b3bec03a833fa40e406f36ed2e23a35a59d1068f"
|
||||
integrity sha512-I3d77tQyaSlOx/6vurDDLk7upb5GA2ldEtVXkk7Kn5cy+tLlS0KlqDK14xUxlxh7jz4StjgKcwFyrpymsPpomA==
|
||||
|
||||
react-dom@^18.0.0:
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0.tgz#26b88534f8f1dbb80853e1eabe752f24100d8023"
|
||||
@@ -9476,11 +9588,23 @@ react-dom@^18.0.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.21.0"
|
||||
|
||||
react-error-boundary@^3.1.0, react-error-boundary@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
||||
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
react-error-overlay@^6.0.10:
|
||||
version "6.0.10"
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
|
||||
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
|
||||
|
||||
react-fast-compare@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
|
||||
react-firebaseui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-firebaseui/-/react-firebaseui-6.0.0.tgz#97a075e7caa2fb969c8ded9efbbcefccb1f33ce1"
|
||||
@@ -9488,6 +9612,17 @@ react-firebaseui@^6.0.0:
|
||||
dependencies:
|
||||
firebaseui "^6.0.0"
|
||||
|
||||
react-helmet-async@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e"
|
||||
integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
invariant "^2.2.4"
|
||||
prop-types "^15.7.2"
|
||||
react-fast-compare "^3.2.0"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@@ -9595,14 +9730,6 @@ react@^18.0.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
reactfire@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/reactfire/-/reactfire-4.2.1.tgz#48bbd2408247e930658bff58d94863b759381d9a"
|
||||
integrity sha512-wWATeAPnGmp44rkG+0F2788HnVNgsk0H1vYimeNnNa/RouzkOn58vJmYgKyQhWAOmZl9Smkx9bDsFlt3PevN0w==
|
||||
dependencies:
|
||||
rxfire "^6.0.2"
|
||||
rxjs "^6.6.3 || ^7.0.1"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@~2.3.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
@@ -9809,6 +9936,14 @@ resolve@^2.0.0-next.3:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
resolve@~1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
|
||||
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
|
||||
dependencies:
|
||||
is-core-module "^2.1.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
restore-cursor@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
||||
@@ -9844,6 +9979,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@~2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rollup-plugin-terser@^7.0.0:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
|
||||
@@ -9868,20 +10010,6 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
rxfire@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/rxfire/-/rxfire-6.0.3.tgz#556a08e5276563c867daeb8c670f59363d212365"
|
||||
integrity sha512-77nkyffHh7jgfi1YA/N9RI+kWxYpgKk6GRML1lyersvaqbJt4hkvWwk1rWib9Rb5Lr5mT+Ha45lu7nM79sJCZA==
|
||||
dependencies:
|
||||
tslib "^1.9.0 || ~2.1.0"
|
||||
|
||||
"rxjs@^6.6.3 || ^7.0.1":
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.3.0.tgz#39fe4f3461dc1e50be1475b2b85a0a88c1e938c6"
|
||||
integrity sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw==
|
||||
dependencies:
|
||||
tslib "~2.1.0"
|
||||
|
||||
rxjs@^7.5.5:
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f"
|
||||
@@ -10114,6 +10242,11 @@ setprototypeof@1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
shallowequal@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
@@ -10200,6 +10333,24 @@ source-list-map@^2.0.0, source-list-map@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||
|
||||
source-map-explorer@^2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-explorer/-/source-map-explorer-2.5.2.tgz#857cab5dd9d1d7175e9c5c2739dc9ccfb99f2dc5"
|
||||
integrity sha512-gBwOyCcHPHcdLbgw6Y6kgoH1uLKL6hN3zz0xJcNI2lpnElZliIlmSYAjUVwAWnc7+HscoTyh1ScR7ITtFuEnxg==
|
||||
dependencies:
|
||||
btoa "^1.2.1"
|
||||
chalk "^4.1.0"
|
||||
convert-source-map "^1.7.0"
|
||||
ejs "^3.1.5"
|
||||
escape-html "^1.0.3"
|
||||
glob "^7.1.6"
|
||||
gzip-size "^6.0.0"
|
||||
lodash "^4.17.20"
|
||||
open "^7.3.1"
|
||||
source-map "^0.7.3"
|
||||
temp "^0.9.4"
|
||||
yargs "^16.2.0"
|
||||
|
||||
source-map-js@^1.0.1, source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
@@ -10629,6 +10780,14 @@ temp-dir@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
|
||||
integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==
|
||||
|
||||
temp@^0.9.4:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620"
|
||||
integrity sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
rimraf "~2.6.2"
|
||||
|
||||
tempy@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3"
|
||||
@@ -10805,7 +10964,7 @@ tslib@^1.8.1, tslib@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
"tslib@^1.9.0 || ~2.1.0", tslib@^2.0.3, tslib@~2.1.0:
|
||||
tslib@^2.0.3:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
|
||||
Reference in New Issue
Block a user