diff --git a/.gitignore b/.gitignore index 56ebe57..6e69c23 100644 --- a/.gitignore +++ b/.gitignore @@ -215,4 +215,6 @@ sketch firebaseConfig.ts .firebaserc firebase.json -.firebase \ No newline at end of file +.firebase +# Sentry +.sentryclirc diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 5aa0018..a070da6 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,3 +1,4 @@ +
\ No newline at end of file diff --git a/next.config.js b/next.config.js index b42d436..ef9a580 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ +const { withSentryConfig } = require('@sentry/nextjs'); + const nextConfig = { reactStrictMode: true, webpack(config) { @@ -12,4 +14,8 @@ const nextConfig = { }, }; -module.exports = nextConfig; +const sentryWebpackPluginOptions = { + silent: true, +}; + +module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions); diff --git a/next.config.wizardcopy.js b/next.config.wizardcopy.js new file mode 100644 index 0000000..b7cc739 --- /dev/null +++ b/next.config.wizardcopy.js @@ -0,0 +1,35 @@ +// This file sets a custom webpack configuration to use your Next.js app +// with Sentry. +// https://nextjs.org/docs/api-reference/next.config.js/introduction +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +const { withSentryConfig } = require('@sentry/nextjs'); + +const moduleExports = { + reactStrictMode: true, + webpack(config) { + config.module.rules.push({ + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }); + + return config; + }, +}; + +const sentryWebpackPluginOptions = { + // Additional config options for the Sentry Webpack plugin. Keep in mind that + // the following options are set automatically, and overriding them is not + // recommended: + // release, url, org, project, authToken, configFile, stripPrefix, + // urlPrefix, include, ignore + + silent: true, // Suppresses all logs + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options. +}; + +// Make sure adding Sentry options is the last code to run before exporting, to +// ensure that your source maps include changes from all other Webpack plugins +module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions); diff --git a/package.json b/package.json index 264b1b5..3ce0881 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@reduxjs/toolkit": "^1.8.0", + "@sentry/nextjs": "^6.19.2", "@svgr/webpack": "^5.5.0", "@types/react-redux": "^7.1.23", "@types/yup": "^0.29.13", @@ -44,6 +45,7 @@ "@storybook/testing-library": "^0.0.9", "@types/node": "17.0.21", "@types/react": "17.0.40", + "@types/react-dom": "^17.0.14", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", "babel-loader": "^8.2.3", diff --git a/sentry.client.config.js b/sentry.client.config.js new file mode 100644 index 0000000..33a51b8 --- /dev/null +++ b/sentry.client.config.js @@ -0,0 +1,17 @@ +// This file configures the initialization of Sentry on the browser. +// The config you add here will be used whenever a page is visited. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; + +Sentry.init({ + dsn: SENTRY_DSN || 'https://fc2400a639f64961be0888ef06fb9196@o1128064.ingest.sentry.io/6170372', + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}); diff --git a/sentry.properties b/sentry.properties new file mode 100644 index 0000000..2277954 --- /dev/null +++ b/sentry.properties @@ -0,0 +1,4 @@ +defaults.url=https://sentry.io/ +defaults.org=teamcooks +defaults.project=teamcooks +cli.executable=../../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli diff --git a/sentry.server.config.js b/sentry.server.config.js new file mode 100644 index 0000000..4fecb8d --- /dev/null +++ b/sentry.server.config.js @@ -0,0 +1,17 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; + +Sentry.init({ + dsn: SENTRY_DSN || 'https://fc2400a639f64961be0888ef06fb9196@o1128064.ingest.sentry.io/6170372', + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1.0, + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps +}); diff --git a/src/components/Auth/Auth.stories.tsx b/src/components/Auth/Auth.stories.tsx index 7f404a1..89eef36 100644 --- a/src/components/Auth/Auth.stories.tsx +++ b/src/components/Auth/Auth.stories.tsx @@ -1,14 +1,12 @@ import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Auth } from './Auth'; +import { StoreProvider } from 'store'; +import { AuthContainer } from './Auth'; export default { - title: 'Auth', - component: Auth, - args: { currentForm: 'signin' }, -} as ComponentMeta; + title: 'AuthContainer', + component: AuthContainer, +} as ComponentMeta; -const Template: ComponentStory = (args) => ; +const Template: ComponentStory = () => ; export const SignInForm = Template.bind({}); -export const SignUpForm = Template.bind({}); -SignUpForm.args = { currentForm: 'signup' }; diff --git a/src/components/Auth/Auth.styled.tsx b/src/components/Auth/Auth.styled.tsx index dc61c28..7cf8f4a 100644 --- a/src/components/Auth/Auth.styled.tsx +++ b/src/components/Auth/Auth.styled.tsx @@ -1,15 +1,10 @@ import styled from '@emotion/styled'; import { pxToRem } from 'utils'; -import { StyleInputProps } from './Auth.types'; +import { StyledInputProps } from './Auth.types'; export const StyledAuthContainer = styled.div` - padding: 15vh 0 0 0; - > * { - max-width: ${pxToRem(400)}; - display: block; - width: 40vw; - min-width: ${pxToRem(300)}; - margin: 0 auto; + h1 { + text-align: center; } `; @@ -19,8 +14,8 @@ export const StyledForm = styled.form` display: flex; flex-direction: column; gap: ${pxToRem(4)}; - input, - button { + > * { + display: block; padding: 0 ${pxToRem(24)}; height: ${pxToRem(36)}; border: none; @@ -28,6 +23,18 @@ export const StyledForm = styled.form` } `; +export const StyledAuthError = styled.div` + border: 2px solid red; + background-color: rgba(255, 0, 0, 0.4); + border-radius: ${pxToRem(5)}; + padding: ${pxToRem(16)} 0; + margin: ${pxToRem(18)} auto; + max-width: ${pxToRem(400)}; + min-width: ${pxToRem(280)}; + text-align: center; + color: white; +`; + export const StyledFieldError = styled.div` height: ${pxToRem(32)}; line-height: 1.8; @@ -36,7 +43,7 @@ export const StyledFieldError = styled.div` padding: 0 ${pxToRem(20)}; `; -export const StyledInput = styled.input` +export const StyledInput = styled.input` padding: 0 ${pxToRem(24)}; height: ${pxToRem(36)}; border: none; diff --git a/src/components/Auth/Auth.tsx b/src/components/Auth/Auth.tsx index 3148cf2..cef4108 100644 --- a/src/components/Auth/Auth.tsx +++ b/src/components/Auth/Auth.tsx @@ -1,47 +1,83 @@ import { FormikProps, withFormik } from 'formik'; -import { FormValues, FormProps } from './Auth.types'; -import { StyledForm, StyledInput, StyledFieldError, StyledAuthContainer } from './Auth.styled'; -import { AUTH_FUNC, SCHEMA, INITIAL_VALUES, FIELDS, HEADING, PLACEHOLDER, TYPE } from './AuthServices'; -import { Button } from 'components/Button/Button'; +import { FormValues, FormProps, AuthContainerProps } from './Auth.types'; +import { StyledForm, StyledInput, StyledAuthError, StyledFieldError, StyledAuthContainer } from './Auth.styled'; +import { + AUTH_STATE, + AUTH_FUNC, + SCHEMA, + INITIAL_VALUES, + FIELDS, + HEADING, + PLACEHOLDER, + TYPE, + AUTH_ERROR_MSG, +} from './AuthServices'; +import { Button, Heading } from 'components'; +import { useState, Fragment } from 'react'; +import { useDispatch } from 'react-redux'; +import { actions } from 'store/slices/auth'; const AuthForm = (props: FormProps & FormikProps): JSX.Element => { const { currentForm, values, errors, dirty, touched, isValid, handleChange, handleBlur, handleSubmit } = props; + return ( - - - {FIELDS[currentForm].map( - (field): JSX.Element => ( - <> - - - {touched[field] && errors[field]} - - ), - )} + + {FIELDS[currentForm].map( + (field): JSX.Element => ( + + + + {touched[field] && errors[field]} + + ), + )} - - - + + ); }; -export const Auth = withFormik({ +const Auth = withFormik({ mapPropsToValues: ({ currentForm }) => INITIAL_VALUES[currentForm], validationSchema: ({ currentForm }: FormProps) => SCHEMA[currentForm], - handleSubmit(values: FormValues, { props: { currentForm } }) { - AUTH_FUNC[currentForm](values); + handleSubmit: async (values: FormValues, { props: { onSubmit } }) => { + onSubmit(values); }, })(AuthForm); + +export const AuthContainer = ({ onClose }: AuthContainerProps) => { + const [currentForm, setCurrentForm] = useState(AUTH_STATE.signin); + const [hasAuthError, setAuthError] = useState(false); + const dispatch = useDispatch(); + + const handleSubmit = async (values) => { + try { + dispatch(actions.loading(true)); + const { uid: userId } = await AUTH_FUNC[currentForm](values); + dispatch(actions.signIn(userId)); + onClose(); + } catch (e) { + setAuthError(true); + } + }; + return ( + + {HEADING[currentForm]} + {hasAuthError && {AUTH_ERROR_MSG[currentForm]}} + + + ); +}; diff --git a/src/components/Auth/Auth.types.ts b/src/components/Auth/Auth.types.ts index 471488b..2e7f7ca 100644 --- a/src/components/Auth/Auth.types.ts +++ b/src/components/Auth/Auth.types.ts @@ -16,8 +16,14 @@ export interface FormProps { initialPasswordConfirm?: string; initialUsername?: string; currentForm: 'signin' | 'signup'; + onSubmit: (values: {}) => void; } -export interface StyleInputProps { +export interface StyledInputProps { $warning: boolean; } + + +export interface AuthContainerProps { + onClose: () => void; +} \ No newline at end of file diff --git a/src/components/Auth/AuthServices.ts b/src/components/Auth/AuthServices.ts index b81ebbd..5e4163b 100644 --- a/src/components/Auth/AuthServices.ts +++ b/src/components/Auth/AuthServices.ts @@ -1,6 +1,11 @@ import { signIn, signUp } from 'api/requestAuth'; import * as Yup from 'yup'; +export const AUTH_STATE = { + signin: 'signin', + signup: 'signup', +} as const; + export const TOGGLE_MESSAGE = { signin: 'Not registered yet? Sign up here!', signup: 'Already a member? Sign in here!', diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx index 0abbb48..32f1740 100644 --- a/src/components/Dialog/Dialog.stories.tsx +++ b/src/components/Dialog/Dialog.stories.tsx @@ -1,4 +1,5 @@ import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { AuthContainer } from 'components'; import { Dialog } from './Dialog'; export default { @@ -6,7 +7,7 @@ export default { component: Dialog, args: { onClose: () => console.log('closed'), - children:

hahaha

, + children:

lalalalalalala

, nodeId: 'dialog', label: 'test', }, @@ -16,6 +17,44 @@ const Template: ComponentStory = (args) => ; export const DefaultDialog = Template.bind({}); -export const TestDialog = Template.bind({}); +export const AuthDialog = Template.bind({}); -TestDialog.args = { ...DefaultDialog.args, children:

TESTTEST

}; +AuthDialog.args = { ...DefaultDialog.args, children: }; + +export const LongDialog = Template.bind({}); +LongDialog.args = { + ...DefaultDialog.args, + children: ( +

+ 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 야무와, 한글 로렘입숨. 모를 어둠에 흘러가는 않을뿐더러 있었다. 위에 떠올랐는지 K는 구름처럼, 띄지 그뭄달이 + 저녁에야, 사슬을 노을이 듯싶었다. 도착했다. 너머로? K는 섬들은 노을이 있었다. 흐렸다. 있는! 성이 도착했다. 버려진 + 노을이 않을뿐더러! 야무와, 한글 로렘입숨. 성이, 사슬을 조차 희멀건, 언제 어두워지는 산은 것을 너머로 바다에 + 저녁에야 그뭄달이 희미한 몸을 알리는 섬들은 듯싶었다., 숲에 꽃피는 K는 그뭄달빛은 눈에 성은. 않을뿐더러 바다에 큰 + 있었다. 있는? +

+ ), +}; diff --git a/src/components/Dialog/Dialog.styled.tsx b/src/components/Dialog/Dialog.styled.tsx index 50573c8..0609068 100644 --- a/src/components/Dialog/Dialog.styled.tsx +++ b/src/components/Dialog/Dialog.styled.tsx @@ -1,31 +1,39 @@ import styled from '@emotion/styled'; import { IconButton } from 'components'; +import { media } from 'utils'; export const StyledDialogContainer = styled.div` z-index: 200; - position: fixed; - top: 0; - width: 90%; - height: 90%; + position: absolute; + top: 50vh; + left: 50%; + ${media.desktop} { + width: 60vw; + } + ${media.mobile} { + width: 90vw; + } + min-height: 50vh; + max-height: 80vh; + transform: translate(-50%, -50%); overflow: auto; -`; + background-color: rgba(0, 0, 0, 0.5); + `; export const StyledDialogContent = styled.div` z-index: 200; - color: #121212; - background: rgba(36, 36, 36, 0.8); - backdrop-filter: blur(3px); - min-height: 100%; + padding: 20px; + margin: 20px; `; export const StyledCloseButton = styled(IconButton)` cursor: pointer; position: absolute; z-index: 200; - top: 20px; - right: 20px; + top: 5px; + right: 5px; border: 0; - padding: 10px; + padding: 5px; background: transparent; color: #fefefe; svg { diff --git a/src/components/EmptyPage/EmptyPage.styled.tsx b/src/components/EmptyPage/EmptyPage.styled.tsx index 1084faf..32f88b0 100644 --- a/src/components/EmptyPage/EmptyPage.styled.tsx +++ b/src/components/EmptyPage/EmptyPage.styled.tsx @@ -6,13 +6,13 @@ export const StyledContainer = styled.div` padding-top: 20vh; display: flex; flex-direction: column; - justify-contents: center; - align-items; center; + justify-content: center; + align-items: center; text-align: center; gap: 10vh; h2 { text-align: center; - color: ${props => props.theme.color.primaryOrange}; + color: ${({ theme }) => theme.color.primaryOrange}; font-size: ${pxToRem(40)}; } `; diff --git a/src/components/ErrorBoundary/ErrorBoundary.stories.tsx b/src/components/ErrorBoundary/ErrorBoundary.stories.tsx new file mode 100644 index 0000000..dd64fe6 --- /dev/null +++ b/src/components/ErrorBoundary/ErrorBoundary.stories.tsx @@ -0,0 +1,16 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ErrorBoundary } from './ErrorBoundary'; + +export default { + title: 'ErrorBoundary', + component: ErrorBoundary, + args: { + children:

NoError

, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + +export const Error = Template.bind({}); diff --git a/src/components/ErrorBoundary/ErrorBoundary.tsx b/src/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 0000000..d949d5c --- /dev/null +++ b/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Heading, Header, EmptyPage } from '..'; +import { ErrorBoundaryProps, ErrorBoundaryState } from './ErrorBoundary.types'; +import Link from 'next/link'; + +export class ErrorBoundary extends React.Component { + public state: ErrorBoundaryState = { + hasError: false, + }; + + public componentDidCatch(error: Error) { + this.setState({ + hasError: true, + }); + console.error('===================================='); + console.error(error); + console.error('===================================='); + } + + public render() { + if (this.state.hasError) { + return ( + <> +
+ + Something went wrong... + + Go back to main page? + + + + ); + } + return this.props.children; + } +} diff --git a/src/components/ErrorBoundary/ErrorBoundary.types.ts b/src/components/ErrorBoundary/ErrorBoundary.types.ts new file mode 100644 index 0000000..8438c94 --- /dev/null +++ b/src/components/ErrorBoundary/ErrorBoundary.types.ts @@ -0,0 +1,9 @@ +import React from 'react'; + +export interface ErrorBoundaryProps extends WithRouterProps { + children?: React.ReactNode; +} + +export interface ErrorBoundaryState { + hasError: boolean; +} diff --git a/src/components/Header/Header.styled.tsx b/src/components/Header/Header.styled.tsx index 417d864..d443e41 100644 --- a/src/components/Header/Header.styled.tsx +++ b/src/components/Header/Header.styled.tsx @@ -1,22 +1,20 @@ import styled from '@emotion/styled'; import { IconButton } from 'components'; +import { StyledHeaderProps } from './Header.types'; -const headerHeight = 70; +export const headerHeight = 70; -export const StyledHeader = styled.header` +export const StyledHeader = styled.header` background-color: ${({ theme }) => theme.color.white}; box-shadow: 0 4px 10px rgba(0 0 0 / 10%); padding: 0 10px 0 20px; position: fixed; top: 0; + ${({ $hide }) => $hide && `top: ${-1 * headerHeight}px;`} left: 0; right: 0; transition: top 0.2s ease-in-out; z-index: 10; - - &.hide { - top: ${-1 * headerHeight}px; - } `; export const StyledDiv = styled.div` diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 96f09c3..ac52140 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,21 +1,19 @@ -import { SearchForm, Menu, Button, Logo } from 'components'; +import { SearchForm, Menu, Button, Logo, AuthContainer, Dialog } from 'components'; import { useState, useEffect, useRef } from 'react'; -// import { useAuthLoading, useAuthUser } from '../../contexts/AuthContext'; import lodash from 'lodash'; import { createPortal } from 'react-dom'; -import { StyledHeader, StyledDiv, StyledIconButton } from './Header.styled'; +import { StyledHeader, StyledDiv, StyledIconButton, headerHeight } from './Header.styled'; +import { useSelector } from 'react-redux'; +import { RootState } from 'store'; +import { AuthState } from 'store/slices/auth'; export const Header = (): JSX.Element => { - // const authLoading = useAuthLoading(); - // const authUser = useAuthUser(); - // const [showDialog, setShowDialog] = useState(false); - - const [tempAuth, setTempAuth] = useState(false); + const [showDialog, setShowDialog] = useState(false); const [hideHeader, setHideHeader] = useState(false); const [showScrollToTop, setShowScrollToTop] = useState(false); const oldScrollTop = useRef(0); + const { authUser, isLoading } = useSelector((state) => state.auth); - /* const handleOpenDialog = () => { setShowDialog(true); }; @@ -23,19 +21,18 @@ export const Header = (): JSX.Element => { const handleCloseDialog = () => { setShowDialog(false); }; - */ const handleFocus = () => { setHideHeader(false); }; const handleBlur = () => { - setHideHeader(window.pageYOffset > 70); + setHideHeader(window.pageYOffset > headerHeight); }; const controlHeader = lodash.throttle(() => { const currentScrollTop = window.pageYOffset; - setHideHeader(currentScrollTop > 70 && currentScrollTop > oldScrollTop.current); + setHideHeader(currentScrollTop > headerHeight && currentScrollTop > oldScrollTop.current); oldScrollTop.current = currentScrollTop; }, 300); @@ -54,11 +51,11 @@ export const Header = (): JSX.Element => { }, []); return ( - + - {tempAuth ? ( + {authUser ? ( ) : ( <> @@ -68,12 +65,16 @@ export const Header = (): JSX.Element => { aria-haspopup="dialog" aria-label="Open SignIn Dialog" title="Open SignIn Dialog" - // onClick={handleOpenDialog} + onClick={handleOpenDialog} color="black" > Sign In - {/* */} + {showDialog && ( + + + + )} )} {showScrollToTop && diff --git a/src/components/Header/Header.types.ts b/src/components/Header/Header.types.ts new file mode 100644 index 0000000..88be5c9 --- /dev/null +++ b/src/components/Header/Header.types.ts @@ -0,0 +1,3 @@ +export interface StyledHeaderProps { + $hide: boolean; +} \ No newline at end of file diff --git a/src/components/Layout/Layout.styled.tsx b/src/components/Layout/Layout.styled.tsx new file mode 100644 index 0000000..ce291ac --- /dev/null +++ b/src/components/Layout/Layout.styled.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; +import { headerHeight } from 'components/Header/Header.styled'; +import { media, pxToRem } from 'utils'; + +export const StyledMain = styled.main` + margin-top: ${headerHeight}px; + ${media.mobile} { + padding: ${pxToRem(10)}; + min-width: 320px; + } + ${media.desktop} { + max-width: 1500px; + margin: ${headerHeight}px auto 0; + } +`; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index e916e60..59f3473 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,11 +1,12 @@ import { Header } from '..'; +import { StyledMain } from './Layout.styled'; import { LayoutProps } from './Layout.types'; export const Layout = ({ children }: LayoutProps): JSX.Element => { return ( <>
-
{children}
+ {children} ); }; diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 6aa699d..e126162 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -4,10 +4,12 @@ import { useRouter } from 'next/router'; import { IconButton, Button } from 'components'; import Link from 'next/link'; import { logOut } from 'api/requestAuth'; +import { useDispatch } from 'react-redux'; +import { actions } from 'store/slices/auth'; export const Menu = () => { const [isOpen, setIsOpen] = useState(false); - +const dispatch = useDispatch(); const handleClick = () => { setIsOpen(!isOpen); }; @@ -18,6 +20,10 @@ export const Menu = () => { } }; + const handleSignOut = () => { + logOut(); + dispatch(actions.signOut()); + } const router = useRouter(); /* @@ -54,7 +60,7 @@ export const Menu = () => { My Recipes - diff --git a/src/components/Pagination/Pagination.styled.tsx b/src/components/Pagination/Pagination.styled.tsx index cb9d08f..5192b8b 100644 --- a/src/components/Pagination/Pagination.styled.tsx +++ b/src/components/Pagination/Pagination.styled.tsx @@ -3,12 +3,12 @@ import { pxToRem } from 'utils'; export const StyledPaginationControl = styled.div` display: flex; - justify-contents: center; + justify-content: center; align-items: center; gap: ${pxToRem(20)}; ul { display: flex; - justify-contents: center; + justify-content: center; align-items: center; gap: ${pxToRem(10)}; } diff --git a/src/components/index.ts b/src/components/index.ts index 46add14..9ea4a6c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,11 +6,14 @@ export * from './Logo/Logo'; export * from './Loading/Loading'; export * from './Heading/Heading'; export * from './Layout/Layout'; +export * from './Dialog/Dialog'; export * from './Menu/Menu'; export * from './Header/Header'; export * from './EmptyPage/EmptyPage'; export * from './SearchForm/SearchForm'; export * from './SkeletonCard/SkeletonCard'; +export * from './Auth/Auth'; +export * from './ErrorBoundary/ErrorBoundary'; export * from './Card/Card'; export * from './RandomRecipe/RandomRecipe'; export * from './HotRecipes/HotRecipes'; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index b1382ac..796385c 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,17 +2,19 @@ import type { AppProps } from 'next/app'; import { ThemeProvider } from '@emotion/react'; import { theme } from 'theme/theme'; import { GlobalStyle } from 'styles/GlobalStyle'; -import { Layout } from '../components'; import { StoreProvider } from 'store'; +import { Layout, ErrorBoundary } from 'components'; function MyApp({ Component, pageProps }: AppProps) { return ( - - - + + + + + ); diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx new file mode 100644 index 0000000..823b3a4 --- /dev/null +++ b/src/pages/_error.tsx @@ -0,0 +1,64 @@ +import NextErrorComponent from 'next/error'; +import * as Sentry from '@sentry/nextjs'; + +const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => { + if (!hasGetInitialPropsRun && err) { + // getInitialProps is not called in case of + // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass + // err via _app.js so it can be captured + Sentry.captureException(err); + // Flushing is not required in this case as it only happens on the client + } + + return ; +}; + +MyError.getInitialProps = async (context) => { + const errorInitialProps = await NextErrorComponent.getInitialProps(context); + + const { res, err, asPath } = context; + + // Workaround for https://github.com/vercel/next.js/issues/8592, mark when + // getInitialProps has run + errorInitialProps.hasGetInitialPropsRun = true; + + // Returning early because we don't want to log 404 errors to Sentry. + if (res?.statusCode === 404) { + return errorInitialProps; + } + + // Running on the server, the response object (`res`) is available. + // + // Next.js will pass an err on the server if a page's data fetching methods + // threw or returned a Promise that rejected + // + // Running on the client (browser), Next.js will provide an err if: + // + // - a page's `getInitialProps` threw or returned a Promise that rejected + // - an exception was thrown somewhere in the React lifecycle (render, + // componentDidMount, etc) that was caught by Next.js's React Error + // Boundary. Read more about what types of exceptions are caught by Error + // Boundaries: https://reactjs.org/docs/error-boundaries.html + + if (err) { + Sentry.captureException(err); + + // Flushing before returning is necessary if deploying to Vercel, see + // https://vercel.com/docs/platform/limits#streaming-responses + await Sentry.flush(2000); + + return errorInitialProps; + } + + // If this point is reached, getInitialProps was called without any + // information about what the error might be. This is unexpected and may + // indicate a bug introduced in Next.js, so record it in Sentry + Sentry.captureException( + new Error(`_error.js getInitialProps missing data at path: ${asPath}`), + ); + await Sentry.flush(2000); + + return errorInitialProps; +}; + +export default MyError; diff --git a/src/pages/errorTest.tsx b/src/pages/errorTest.tsx new file mode 100644 index 0000000..99fa8d9 --- /dev/null +++ b/src/pages/errorTest.tsx @@ -0,0 +1,22 @@ +import { NextPage } from 'next'; +import { Button, Heading } from 'components'; + +const ErrorTest: NextPage = () => { + return ( + <> + Normal Page + + + ); +}; + +export default ErrorTest; diff --git a/src/pages/search/[keyword].tsx b/src/pages/search/[keyword].tsx index 5daedd2..0bb0074 100644 --- a/src/pages/search/[keyword].tsx +++ b/src/pages/search/[keyword].tsx @@ -2,9 +2,11 @@ import { useRouter } from 'next/router'; import { NextPage } from 'next'; import { useSearchRecipeQuery } from 'store/services'; import { useState } from 'react'; +import { ContextProp, SearchPageProps } from './search.types'; const RESULTS_PER_PAGE = 12; -const Search: NextPage = ({ data }) => { +const Search: NextPage = ({ results, totalResults }: SearchPageProps) => { + // const { // query: { keyword }, // } = useRouter(); @@ -17,8 +19,9 @@ const Search: NextPage = ({ data }) => { // console.log(data); return (
+

{totalResults}

    - {data.map(({ id, title }) => ( + {results.map(({ id, title }) => (
  • {title}
  • ))}
@@ -26,22 +29,21 @@ const Search: NextPage = ({ data }) => { ); }; -export async function getServerSideProps(context) { - const { keyword } = context.query; - const { results: data } = await fetch(`https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com//recipes/search`, { - headers: { - 'content-type': 'application/json', - 'x-rapidapi-host': process.env.NEXT_PUBLIC_RAPID_API_HOST, - 'x-rapidapi-key': process.env.NEXT_PUBLIC_RAPID_API_KEY, - }, - params: { - query: keyword, - number: RESULTS_PER_PAGE, - offset: 0, - }, - }).then((res) => res.json()); +export async function getServerSideProps({ query }: ContextProp) { + const { keyword } = query; + const { results, totalResults } = await fetch( + `https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com//recipes/search?query=${keyword}&number=${RESULTS_PER_PAGE}&offset=${0}`, + { + headers: { + 'content-type': 'application/json', + 'x-rapidapi-host': process.env.NEXT_PUBLIC_RAPID_API_HOST, + 'x-rapidapi-key': process.env.NEXT_PUBLIC_RAPID_API_KEY, + }, + } as RequestInit, + ).then((res) => res.json()); return { - props: { data }, + props: { results, totalResults }, }; } + export default Search; diff --git a/src/pages/search/search.types.ts b/src/pages/search/search.types.ts new file mode 100644 index 0000000..2db65f7 --- /dev/null +++ b/src/pages/search/search.types.ts @@ -0,0 +1,18 @@ +export interface ContextProp { + query: { keyword: string }; +} + +interface SearchResult { + id: number; + image: string; + openLicense: number; + readyInMinutes: number; + servings: number; + sourceUrl: string; + title: string; +} + +export interface SearchPageProps { + results: SearchResult[]; + totalResults: number; +} diff --git a/src/store/index.tsx b/src/store/index.tsx index 1d30656..0bdd765 100644 --- a/src/store/index.tsx +++ b/src/store/index.tsx @@ -2,10 +2,12 @@ import { configureStore } from '@reduxjs/toolkit'; import { ReactNode } from 'react'; import { Provider } from 'react-redux'; import { twoSpoonApi } from 'store/services'; +import { reducer as authReducer } from './slices/auth'; export const store = configureStore({ reducer: { [twoSpoonApi.reducerPath]: twoSpoonApi.reducer, + auth: authReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(twoSpoonApi.middleware), }); @@ -13,3 +15,5 @@ export const store = configureStore({ export const StoreProvider = (props: { children: ReactNode }) => { return ; }; + +export type RootState = ReturnType; diff --git a/src/store/slices/auth.ts b/src/store/slices/auth.ts new file mode 100644 index 0000000..9863eb3 --- /dev/null +++ b/src/store/slices/auth.ts @@ -0,0 +1,18 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export interface AuthState { + isLoading: boolean; + authUser: null | string; +} + +const AuthSlice = createSlice({ + name: 'Auth', + initialState: { isLoading: false, authUser: null }, + reducers: { + loading: (state: AuthState, action: PayloadAction) => ({ ...state, isLoading: action.payload }), + signIn: (state: AuthState, action: PayloadAction) => ({ authUser: action.payload, isLoading: false }), + signOut: (state: AuthState) => ({ authUser: null, isLoading: false }), + }, +}); + +export const { actions, reducer } = AuthSlice; diff --git a/yarn.lock b/yarn.lock index 29d45e3..9910224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1089,7 +1089,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.15.4": +"@babel/runtime@^7.12.8", "@babel/runtime@^7.15.4": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== @@ -2153,6 +2153,140 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== +"@sentry/browser@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.19.2.tgz#c0f6df07584f3b36fa037067aea20b2c8c2095a3" + integrity sha512-5VC44p5Vu2eJhVT39nLAJFgha5MjHDYCyZRR1ieeZt3a++otojPGBBAKNAtrEMGV+A2Z9AoneD6ZnDVlyb3GKg== + dependencies: + "@sentry/core" "6.19.2" + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + tslib "^1.9.3" + +"@sentry/cli@^1.73.0": + version "1.74.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.74.2.tgz#3821ec2fe1a027c6bb6c802ea3ae4f63a21e2533" + integrity sha512-J1P66/4yNN55PMO70+QQXeS87r4O9nYuJqZ29HJCPA/ih57jbMFRw9Wp9XLuO/QtpF8Yl7FvOwya/nImTOVOkA== + dependencies: + https-proxy-agent "^5.0.0" + mkdirp "^0.5.5" + node-fetch "^2.6.7" + npmlog "^4.1.2" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + +"@sentry/core@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.2.tgz#dd35ba6ca41a2dd011c43f732bcdadbb52c06376" + integrity sha512-yu1R3ewBT4udmB4v7sc4biQZ0Z0rfB9+TzB5ZKoCftbe6kqXjFMMaFRYNUF9HicVldKAsBktgkWw3+yfqGkw/A== + dependencies: + "@sentry/hub" "6.19.2" + "@sentry/minimal" "6.19.2" + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + tslib "^1.9.3" + +"@sentry/hub@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.2.tgz#0e9f9c507e55d8396002f644b43ef27cc9ff1289" + integrity sha512-W7KCgNBgdBIMagOxy5J5KQPe+maYxSqfE8a5ncQ3R8BcZDQEKnkW/1FplNbfRLZqA/tL/ndKb7pTPqVtzsbARw== + dependencies: + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + tslib "^1.9.3" + +"@sentry/integrations@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.19.2.tgz#d5abcab94ae23ada097eb454c269e9ab573e14bb" + integrity sha512-RjkZXPrtrM+lJVEa4OpZ9CYjJdkpPoWslEQzLMvbaRpURpHFqmABGtXRAnJRYKmy6h7/9q9sABcDgCD4OZw11g== + dependencies: + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + localforage "^1.8.1" + tslib "^1.9.3" + +"@sentry/minimal@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.19.2.tgz#e748541e4adbc7e80a3b6ccaf01b631c17fc44b4" + integrity sha512-ClwxKm77iDHET7kpzv1JvzDx1er5DoNu+EUjst0kQzARIrXvu9xuZuE2/CnBWycQWqw8o3HoGoKz65uIhsUCzQ== + dependencies: + "@sentry/hub" "6.19.2" + "@sentry/types" "6.19.2" + tslib "^1.9.3" + +"@sentry/nextjs@^6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-6.19.2.tgz#5afebc843de35460a2bc7ddba42ec70003352dea" + integrity sha512-qB0iZZ62NLmwskJzDXoVi2L9jU3l+fI4v2klgexaUOfGBOUI4U/8H2l7orPcZVp6Vsh8PNRB88Vy6mAkfwGiYA== + dependencies: + "@sentry/core" "6.19.2" + "@sentry/hub" "6.19.2" + "@sentry/integrations" "6.19.2" + "@sentry/node" "6.19.2" + "@sentry/react" "6.19.2" + "@sentry/tracing" "6.19.2" + "@sentry/utils" "6.19.2" + "@sentry/webpack-plugin" "1.18.8" + tslib "^1.9.3" + +"@sentry/node@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.19.2.tgz#cad621ad319f555826110f4d6c972a2fc95800fc" + integrity sha512-Z1qREpTpYHxaeWjc1zMUk8ZTAp1WbxMiI2TVNc+a14DVT19Z2xNXb06MiRfeLgNc9lVGdmzR62dPmMBjVgPJYg== + dependencies: + "@sentry/core" "6.19.2" + "@sentry/hub" "6.19.2" + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/react@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.19.2.tgz#67760aed06d7e54a2e117cd9048ad19d573c78f1" + integrity sha512-6ffifcUWJegvC5iYJlXL3zBirR05F/i5nA7QaYSMERJqZpXuYhwNPySbuiNTajm64+HA1RbdQkiwrHE/Ur3f1w== + dependencies: + "@sentry/browser" "6.19.2" + "@sentry/minimal" "6.19.2" + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + hoist-non-react-statics "^3.3.2" + tslib "^1.9.3" + +"@sentry/tracing@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.19.2.tgz#ed6ff1bc901c4d79ef97f77ed54ce58c650e4915" + integrity sha512-rGoPpP1JIAxdq5bzrww0XuNVr6yn7RN6/wUcaxf6CAvklKvDx+q28WTGlZLGTZ/3un8Rv6i1FZFZOXizgnVnrg== + dependencies: + "@sentry/hub" "6.19.2" + "@sentry/minimal" "6.19.2" + "@sentry/types" "6.19.2" + "@sentry/utils" "6.19.2" + tslib "^1.9.3" + +"@sentry/types@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.2.tgz#0219c9da21ed975951108b8541913b1966464435" + integrity sha512-XO5qmVBdTs+7PdCz7fAwn1afWxSnRE2KLBFg5/vOdKosPSSHsSHUURSkxiEZc2QsR+JpRB4AeQ26AkIRX38qTg== + +"@sentry/utils@6.19.2": + version "6.19.2" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.2.tgz#995efb896c5159369509f4896c27a2d2ea9191f2" + integrity sha512-2DQQ2OJaxjtyxGq5FmMlqb6hptsqMs2xoBiVRMkTS/rvyTrk1oQdKZ8ePwjtgX3nJ728ni3IXIyXV+vfGp4EBw== + dependencies: + "@sentry/types" "6.19.2" + tslib "^1.9.3" + +"@sentry/webpack-plugin@1.18.8": + version "1.18.8" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.18.8.tgz#247a73a0aa9e28099a736bbe89ca0d35cbac7636" + integrity sha512-PtKr0NL62b5L3kPFGjwSNbIUwwcW5E5G6bQxAYZGpkgL1MFPnS4ND0SAsySuX0byQJRFFium5A19LpzyvQZSlQ== + dependencies: + "@sentry/cli" "^1.73.0" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -3368,6 +3502,13 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/react-dom@^17.0.14": + version "17.0.14" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f" + integrity sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.20", "@types/react-redux@^7.1.23": version "7.1.23" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.23.tgz#3c2bb1bcc698ae69d70735f33c5a8e95f41ac528" @@ -3941,16 +4082,16 @@ app-root-dir@^1.0.2: resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" integrity sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg= +aproba@^1.0.3, aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + "aproba@^1.0.3 || ^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -3996,6 +4137,14 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +are-we-there-yet@~1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -5163,6 +5312,11 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -5370,7 +5524,7 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@^1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -5404,7 +5558,7 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.2: +cookie@0.4.2, cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== @@ -7517,6 +7671,20 @@ gauge@^4.0.0: strip-ansi "^6.0.1" wide-align "^1.1.5" +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + gaxios@^4.0.0: version "4.3.2" resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.2.tgz#845827c2dc25a0213c8ab4155c7a28910f5be83f" @@ -7898,7 +8066,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.1: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -8579,6 +8747,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -9320,6 +9495,13 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -9369,6 +9551,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +localforage@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -9621,6 +9810,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" @@ -10336,6 +10530,16 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" @@ -10375,12 +10579,17 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -11278,7 +11487,7 @@ proxy-agent@^5.0.0: proxy-from-env "^1.0.0" socks-proxy-agent "^5.0.0" -proxy-from-env@^1.0.0: +proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -11674,7 +11883,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -12326,7 +12535,7 @@ serve-static@1.14.2: parseurl "~1.3.3" send "0.17.2" -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -12719,6 +12928,15 @@ string-length@^1.0.0: dependencies: strip-ansi "^3.0.0" +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -13271,7 +13489,7 @@ tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.8.1: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -14031,7 +14249,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.2, wide-align@^1.1.5: +wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==