Revert "issue-129 Add custom auth ui/ux"

This reverts commit fc4906757a.
This commit is contained in:
Kurt Hutten
2020-12-05 18:06:07 +11:00
parent 73e1dccdb8
commit 04820ebd2d
23 changed files with 26 additions and 548 deletions

View File

@@ -18,7 +18,7 @@ export const handler = async (req, _context) => {
email: 'k.hutten@protonmail.ch',
confirmation_sent_at: '2020-10-19T18:09:01Z',
app_metadata: { provider: 'email' },
user_metadata: { full_name: 'sick_dog', userName: 'hi bob' },
user_metadata: { full_name: 'sick_dog' },
created_at: '2020-10-19T18:09:01Z',
updated_at: '2020-10-19T18:09:01Z'
}
@@ -62,12 +62,12 @@ export const handler = async (req, _context) => {
db.user.findOne({
where: { userName: seed },
})
const userNameSeed = enforceAlphaNumeric(user?.user_metadata?.userName)
const userNameSeed = enforceAlphaNumeric(email.split('@')[0])
const userName = await generateUniqueString(userNameSeed, isUniqueCallback) // TODO maybe come up with a better default userName?
const input = {
email,
userName,
name: user?.user_metadata?.full_name,
name: user.user_metadata && user.user_metadata.full_name,
id: user.id,
}
await createUserInsecure({ input })

View File

@@ -70,7 +70,7 @@ import { AuthenticationError, ForbiddenError, parseJWT } from '@redwoodjs/api'
* return await db.user.findOne({ where: { decoded.email } })
* }
*
* @example - User info is contained in the decoded token and roles extracted
* @example - User info is conatined in the decoded token and roles extracted
*
* export const getCurrentUser = async (decoded, { _token, _type }) => {
* return { ...decoded, roles: parseJWT({ decoded }).roles }

View File

@@ -9,7 +9,6 @@
port = 8910
apiProxyPath = "/.netlify/functions"
includeEnvironmentVariables = ['GOOGLE_ANALYTICS_ID']
experimentalFastRefresh = true
[api]
port = 8911
[browser]

View File

@@ -1,15 +0,0 @@
{
"event": "signup",
"instance_id": "403b7d63-17f9-48f1-a85f-3d6b41c7dad1",
"user": {
"id": "641222ee-3e61-4253-8c11-9f764779bcc5",
"aud": "",
"role": "",
"email": "k.hutten2@protonmail.ch",
"confirmation_sent_at": "2020-10-19T18:09:01Z",
"app_metadata": { "provider": "email" },
"user_metadata": { "full_name": "sick_dog", "userName": "hi bob" },
"created_at": "2020-10-19T18:09:01Z",
"updated_at": "2020-10-19T18:09:01Z"
}
}

View File

@@ -14,7 +14,7 @@
},
"dependencies": {
"@material-ui/core": "^4.11.0",
"@redwoodjs/auth": "^0.21.0",
"@redwoodjs/auth": "^0.20.0",
"@redwoodjs/forms": "^0.20.0",
"@redwoodjs/router": "^0.20.0",
"@redwoodjs/web": "^0.20.0",
@@ -22,7 +22,6 @@
"controlkit": "^0.1.9",
"get-active-classes": "^0.0.11",
"golden-layout": "^1.5.9",
"gotrue-js": "^0.9.27",
"jquery": "^3.5.1",
"monaco-editor": "^0.20.0",
"monaco-editor-webpack-plugin": "^1.9.1",

View File

@@ -32,9 +32,6 @@ const Routes = () => {
)
return (
<Router>
<Route path="/account-recovery/update-password" page={UpdatePasswordPage} name="updatePassword" />
{/* <Route path="/account-recovery/update-password/{recoveryToken}" page={UpdatePasswordPage} name="updatePassword" /> */}
<Route path="/account-recovery" page={AccountRecoveryPage} name="accountRecovery" />
<Route path="/" page={PartsPage} name="home" />
<Route notfound page={NotFoundPage} />

View File

@@ -10,7 +10,7 @@ const InputText = ({ value, isEditable, onChange, className }) => {
className
)}
>
<div className="absolute inset-0 mb-2 rounded bg-gray-200 shadow-inners" />
<div className="absolute inset-0 mb-2 rounded bg-gray-200 shadow-inner bg-gray-100" />
<input
className="pl-2 pt-1 text-indigo-800 font-medium mb-px pb-px bg-transparent relative"
onChange={onChange}

View File

@@ -1,36 +0,0 @@
import { getActiveClasses } from 'get-active-classes'
import { TextField, FieldError } from '@redwoodjs/forms'
import { useFormContext } from 'react-hook-form'
// COPY of InputText but using TextField for use with redwood forms. Maybe I should combine them back into one or otherwise try and share code between these two.
const InputText = ({ type = 'text', className, name, validation }) => {
const { errors } = useFormContext()
return (
<>
<div className={getActiveClasses('relative inline-block', className)}>
<FieldError
className="absolute -my-4 text-sm text-red-500 font-ropa-sans"
name={name}
/>
<div
className={getActiveClasses(
'absolute inset-0 mb-2 rounded bg-gray-200 shadow-inner',
{ 'border border-red-500': errors[name] }
)}
/>
<TextField
className={getActiveClasses(
'pl-2 pt-1 text-indigo-800 font-medium mb-px pb-px bg-transparent relative w-full'
)}
name={name}
readOnly={false}
type={type}
validation={validation}
/>
</div>
</>
)
}
export default InputText

View File

@@ -1,7 +0,0 @@
import InputTextForm from './InputTextForm'
export const generated = () => {
return <InputTextForm />
}
export default { title: 'Components/InputTextForm' }

View File

@@ -1,11 +0,0 @@
import { render } from '@redwoodjs/testing'
import InputTextForm from './InputTextForm'
describe('InputTextForm', () => {
it('renders successfully', () => {
expect(() => {
render(<InputTextForm />)
}).not.toThrow()
})
})

View File

@@ -7,18 +7,17 @@ import {
} from './mockEditorParts'
import Svg from 'src/components/Svg'
import OutBound from 'src/components/OutBound'
import { useAuth } from '@redwoodjs/auth'
import ReactGA from 'react-ga'
import LoginModal from 'src/components/LoginModal'
import { useState } from 'react'
const LandingSection = () => {
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false)
const recordedLogin = async () => {
const { logIn } = useAuth()
const recordedLogin = () => {
ReactGA.event({
category: 'login',
action: 'landing section CTA',
})
setIsLoginModalOpen(true)
logIn()
}
return (
<div className="mt-16">
@@ -164,12 +163,10 @@ const LandingSection = () => {
with more integrations coming soon.
</p>
<button
className="bg-texture bg-purple-800 text-center w-full py-6 rounded-b-md border border-indigo-300 border-opacity-0 hover:border-opacity-100 hover:shadow-xl"
className="font-bold text-2xl bg-texture bg-purple-800 text-center w-full py-6 rounded-b-md border border-indigo-300 border-opacity-0 hover:border-opacity-100 hover:shadow-xl"
onClick={recordedLogin}
>
<span className="font-bold text-2xl text-indigo-200">
Start Hacking Now
</span>
<span className="text-indigo-200">Start Hacking Now</span>
</button>
</div>
</div>
@@ -182,11 +179,6 @@ const LandingSection = () => {
/>
</div>
</div>
<LoginModal
open={isLoginModalOpen}
onClose={() => setIsLoginModalOpen(false)}
shouldStartWithSignup
/>
</div>
)
}

View File

@@ -1,183 +0,0 @@
import { useState } from 'react'
import Dialog from '@material-ui/core/Dialog'
import Tab from '@material-ui/core/Tab'
import Tabs from '@material-ui/core/Tabs'
import InputTextForm from 'src/components/InputTextForm'
import OutBound from 'src/components/OutBound'
import { Form, Submit } from '@redwoodjs/forms'
import { useAuth } from '@redwoodjs/auth'
import { Link, routes } from '@redwoodjs/router'
const LoginModal = ({ open, onClose, shouldStartWithSignup = false }) => {
const { logIn, signUp } = useAuth()
const [tab, setTab] = useState(shouldStartWithSignup ? 0 : 1)
const onTabChange = (_, newValue) => {
setTab(newValue)
setError('')
}
const [checkBox, setCheckBox] = useState(true)
const [error, setError] = useState('')
const onSubmitSignUp = async ({ email, password, name, userName }) => {
try {
setError('')
await signUp({
email,
password,
remember: { full_name: name, userName },
})
onClose()
} catch (errorEvent) {
setError(errorEvent?.json?.error_description)
}
}
const onSubmitSignIn = async ({ email, password }) => {
try {
setError('')
await logIn({ email, password, remember: true })
onClose()
} catch (errorEvent) {
setError(errorEvent?.json?.error_description)
}
}
return (
<Dialog open={open} onClose={onClose}>
<div className="bg-gray-100 max-w-2xl rounded-lg shadow-lg">
<Tabs
value={tab}
onChange={onTabChange}
centered
textColor="primary"
indicatorColor="primary"
>
<Tab label="Sign Up" />
<Tab label="Sign In" />
</Tabs>
{error && (
<div className="text-sm text-red-500 font-ropa-sans pt-4 text-center">
{error}
</div>
)}
{tab === 0 ? (
<SignUpForm
onSubmitSignUp={onSubmitSignUp}
checkBox={checkBox}
setCheckBox={setCheckBox}
/>
) : (
<SignInForm onSubmitSignIn={onSubmitSignIn} />
)}
</div>
</Dialog>
)
}
const Field = ({ name, type = 'text', validation }) => (
<>
<span className="capitalize text-gray-500 text-sm align-middle my-3">
{name}:
</span>
<InputTextForm
type={type}
className="text-xl"
name={name}
validation={validation}
/>
</>
)
const HeroButton = ({ text }) => (
<Submit className="bg-texture bg-purple-800 py-6 w-full flex items-center justify-center rounded-b border border-indigo-300 border-opacity-0 hover:border-opacity-100 hover:shadow-xl">
<span className="font-bold text-2xl text-indigo-200">{text}</span>
</Submit>
)
const SignInForm = ({ onSubmitSignIn }) => (
<Form className="w-full" onSubmit={onSubmitSignIn}>
<div className="p-8">
<div
className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }}
>
<Field
name="email"
validation={{
required: true,
pattern: {
value: /[^@]+@[^.]+\..+/,
message: 'please enter a valid email address',
},
}}
/>
<Field
name="password"
type="password"
validation={{ required: true }}
/>
</div>
<Link
to={routes.accountRecovery()}
className="underline text-sm text-gray-500 block text-center"
>
forgot your password?
</Link>
</div>
<HeroButton text="Sign In" />
</Form>
)
const SignUpForm = ({ onSubmitSignUp, checkBox, setCheckBox }) => (
<Form className="w-full" onSubmit={onSubmitSignUp}>
<div className="p-8">
<div
className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }}
>
<Field name="name" validation={{ required: true }} />
<Field
name="userName"
validation={{
required: true,
pattern: {
value: /^[a-zA-Z0-9-_]+$/,
message: 'Only alphanumeric and dash characters allowed',
},
}}
/>
<Field
name="email"
validation={{
required: true,
pattern: {
value: /[^@]+@[^.]+\..+/,
message: 'please enter a valid email address',
},
}}
/>
<Field
name="password"
type="password"
validation={{ required: true }}
/>
</div>
<div className="flex pt-4">
<input
type="checkbox"
checked={checkBox}
onChange={() => setCheckBox(!checkBox)}
/>{' '}
<span className="pl-4 text-gray-500 text-sm max-w-sm">
Stay up-to-date with CadHub's progress with the founder's (
<OutBound className="underline" to="https://twitter.com/IrevDev">
Kurt's
</OutBound>
) newsletter
</span>
</div>
</div>
<HeroButton text="Sign Up" />
</Form>
)
export default LoginModal

View File

@@ -1,7 +0,0 @@
import LoginModal from './LoginModal'
export const generated = () => {
return <LoginModal open={true} />
}
export default { title: 'Components/LoginModal' }

View File

@@ -1,11 +0,0 @@
import { render } from '@redwoodjs/testing'
import LoginModal from './LoginModal'
describe('LoginModal', () => {
it('renders successfully', () => {
expect(() => {
render(<LoginModal />)
}).not.toThrow()
})
})

View File

@@ -1,6 +1,5 @@
import { AuthProvider } from '@redwoodjs/auth'
import GoTrue from 'gotrue-js'
import netlifyIdentity from 'netlify-identity-widget'
import ReactDOM from 'react-dom'
import { RedwoodProvider, FatalErrorBoundary } from '@redwoodjs/web'
import FatalErrorPage from 'src/pages/FatalErrorPage'
@@ -17,6 +16,8 @@ import './cascade/css/main.css'
import 'monaco-editor/min/vs/editor/editor.main.css'
import './index.css'
netlifyIdentity.init()
function initCascadeStudio() {
// if ('serviceWorker' in navigator) {
// navigator.serviceWorker.register('service-worker.js').then(function(registration) {
@@ -48,14 +49,9 @@ function initCascadeStudio() {
}
initCascadeStudio()
const goTrueClient = new GoTrue({
APIUrl: 'https://cadhub.xyz/.netlify/identity',
setCookie: true,
})
ReactDOM.render(
<FatalErrorBoundary page={FatalErrorPage}>
<AuthProvider client={goTrueClient} type="goTrue">
<AuthProvider client={netlifyIdentity} type="netlify">
<RedwoodProvider>
<Routes />
</RedwoodProvider>

View File

@@ -1,12 +1,12 @@
import { useState, useEffect } from 'react'
import { Link, routes, navigate } from '@redwoodjs/router'
import { Link, routes } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { Flash, useQuery, useFlash } from '@redwoodjs/web'
import { Flash } from '@redwoodjs/web'
import Tooltip from '@material-ui/core/Tooltip'
import { useQuery } from '@redwoodjs/web'
import Popover from '@material-ui/core/Popover'
import { getActiveClasses } from 'get-active-classes'
import { useLocation } from '@redwoodjs/router'
import LoginModal from 'src/components/LoginModal'
import ReactGA from 'react-ga'
export const QUERY = gql`
@@ -26,13 +26,11 @@ let previousSubmission = ''
let previousUserID = ''
const MainLayout = ({ children }) => {
const { logOut, isAuthenticated, currentUser, client } = useAuth()
const { addMessage } = useFlash()
const { logIn, logOut, isAuthenticated, currentUser } = useAuth()
const { data, loading } = useQuery(QUERY, {
skip: !currentUser?.sub,
variables: { id: currentUser?.sub },
})
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const [anchorEl, setAnchorEl] = useState(null)
const [popoverId, setPopoverId] = useState(undefined)
@@ -61,7 +59,7 @@ const MainLayout = ({ children }) => {
category: 'login',
action: 'navbar login',
})
setIsLoginModalOpen(true)
logIn()
}
const { pathname, params } = useLocation()
@@ -87,38 +85,6 @@ const MainLayout = ({ children }) => {
previousUserID = currentUser
}
}, [data, currentUser, isAuthenticated])
const hash = window.location.hash
useEffect(() => {
const [key, token] = hash.slice(1).split('=')
if (key === 'confirmation_token') {
console.log('confirming with', token)
client
.confirm(token, true)
.then(() => {
addMessage('Email confirmed', { classes: 'rw-flash-success' })
})
.catch(() => {
addMessage('Problem confirming email', {
classes: 'bg-red-300 text-red-900',
})
})
} else if (key === 'recovery_token') {
// http://localhost:8910/#recovery_token=LwsC-KpgmiDwGo-ZcGH0tA
// https://mandrillapp.com/track/click/31128206/cadhub.xyz?p=eyJzIjoiWFlFbDk5YTVYU0FOSmEtZ3ZtUXlDSW13QURrIiwidiI6MSwicCI6IntcInVcIjozMTEyODIwNixcInZcIjoxLFwidXJsXCI6XCJodHRwczpcXFwvXFxcL2NhZGh1Yi54eXpcXFwvI3JlY292ZXJ5X3Rva2VuPUx3c0MtS3BnbWlEd0dvLVpjR0gwdEFcIixcImlkXCI6XCJjODU1ZWM1MjZiZmQ0Y2JmYmUyNmM4OTk2ZGU4ODczOFwiLFwidXJsX2lkc1wiOltcIjJkMjljMDI0NTY5MzcxODcxMmI0ODA0M2U3MDYyOTA1ZDc1NzVkNjlcIl19In0
// #recovery_token=LwsC-KpgmiDwGo-ZcGH0tA
// navigate(routes.updatePassword({ recoveryToken: token }))
client
.recover(token, true)
.then(() => {
navigate(routes.updatePassword())
})
.catch(() => {
addMessage('Problem recovering account', {
classes: 'bg-red-300 text-red-900',
})
})
}
}, [hash, client]) // complaining about not having addMessage, however adding it puts useEffect into a loop
return (
<>
<header id="cadhub-main-header">
@@ -232,11 +198,7 @@ const MainLayout = ({ children }) => {
)}
</nav>
</header>
<Flash timeout={1500} />
<LoginModal
open={isLoginModalOpen}
onClose={() => setIsLoginModalOpen(false)}
/>
<Flash timeout={1000} />
<main>{children}</main>
</>
)

View File

@@ -1,67 +0,0 @@
import { routes, navigate } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { Form, Submit } from '@redwoodjs/forms'
import { useFlash } from '@redwoodjs/web'
import InputTextForm from 'src/components/InputTextForm'
import MainLayout from 'src/layouts/MainLayout'
import Seo from 'src/components/Seo/Seo'
const AccountRecoveryPage = () => {
const { addMessage } = useFlash()
const { client } = useAuth()
const onSubmit = ({ email }) => {
client
.requestPasswordRecovery(email)
.then(() => {
addMessage('Email sent', { classes: 'rw-flash-success' })
setTimeout(() => {
navigate(routes.home())
}, 500)
})
.catch(() => {
addMessage('Problem sending email', {
classes: 'bg-red-300 text-red-900',
})
})
}
return (
<MainLayout>
<Seo
title="Account recovery"
description="Send recovery email"
lang="en-US"
/>
<section className="max-w-md mx-auto mt-20">
<h2 className="text-xl text-indigo-500 pb-4">Send recovery email</h2>
<Form onSubmit={onSubmit}>
<div
className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }}
>
<span className="capitalize text-gray-500 text-sm align-middle my-3">
email:
</span>
<InputTextForm
className="text-xl"
name="email"
validation={{
required: true,
pattern: {
value: /[^@]+@[^.]+\..+/,
message: 'please enter a valid email address',
},
}}
/>
</div>
<Submit className="bg-indigo-200 text-indigo-800 p-2 px-4 shadow hover:shadow-lg mt-4 rounded">
Send email
</Submit>
</Form>
</section>
</MainLayout>
)
}
export default AccountRecoveryPage

View File

@@ -1,7 +0,0 @@
import AccountRecoveryPage from './AccountRecoveryPage'
export const generated = () => {
return <AccountRecoveryPage />
}
export default { title: 'Pages/AccountRecoveryPage' }

View File

@@ -1,11 +0,0 @@
import { render } from '@redwoodjs/testing'
import AccountRecoveryPage from './AccountRecoveryPage'
describe('AccountRecoveryPage', () => {
it('renders successfully', () => {
expect(() => {
render(<AccountRecoveryPage />)
}).not.toThrow()
})
})

View File

@@ -1,82 +0,0 @@
import { routes, navigate } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { Form, Submit } from '@redwoodjs/forms'
import { useFlash } from '@redwoodjs/web'
import InputTextForm from 'src/components/InputTextForm'
import MainLayout from 'src/layouts/MainLayout'
import Seo from 'src/components/Seo/Seo'
const UpdatePasswordPage = () => {
const { addMessage } = useFlash()
const { client } = useAuth()
const onSubmit = ({ password, confirm }) => {
if (password !== confirm) {
addMessage("Passwords don't match, try again", {
classes: 'bg-red-300 text-red-900',
})
return
}
client
.currentUser()
.update({ password })
.then(() => {
addMessage('Email updated', { classes: 'rw-flash-success' })
setTimeout(() => {
navigate(routes.home())
}, 500)
})
.catch(() => {
addMessage('Problem updating email', {
classes: 'bg-red-300 text-red-900',
})
})
}
return (
<MainLayout>
<Seo
title="Account recovery"
description="Send recovery email"
lang="en-US"
/>
<section className="max-w-md mx-auto mt-20">
<h2 className="text-xl text-indigo-500 pb-4">Reset Password</h2>
<Form onSubmit={onSubmit}>
<div
className="grid items-center gap-2"
style={{ gridTemplateColumns: 'auto 1fr' }}
>
<span className="capitalize text-gray-500 text-sm align-middle my-3">
password:
</span>
<InputTextForm
className="text-xl"
name="email"
type="password"
validation={{
required: true,
}}
/>
<span className="capitalize text-gray-500 text-sm align-middle my-3">
confirm:
</span>
<InputTextForm
className="text-xl"
name="email"
type="password"
validation={{
required: true,
}}
/>
</div>
<Submit className="bg-indigo-200 text-indigo-800 p-2 px-4 shadow hover:shadow-lg mt-4 rounded">
Update
</Submit>
</Form>
</section>
</MainLayout>
)
}
export default UpdatePasswordPage

View File

@@ -1,7 +0,0 @@
import UpdatePasswordPage from './UpdatePasswordPage'
export const generated = () => {
return <UpdatePasswordPage />
}
export default { title: 'Pages/UpdatePasswordPage' }

View File

@@ -1,11 +0,0 @@
import { render } from '@redwoodjs/testing'
import UpdatePasswordPage from './UpdatePasswordPage'
describe('UpdatePasswordPage', () => {
it('renders successfully', () => {
expect(() => {
render(<UpdatePasswordPage />)
}).not.toThrow()
})
})

View File

@@ -2366,10 +2366,10 @@
lodash.omitby "^4.6.0"
merge-graphql-schemas "^1.7.6"
"@redwoodjs/auth@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@redwoodjs/auth/-/auth-0.21.0.tgz#967deef0d0421ea9f6bc205857ad9265d2f5df55"
integrity sha512-o3HuRTs79BqmnZX2zvK6+ffebxJE+T/nqwDDdOmCjXUitSsYbLSJkG4ffUuMtSWCFCxf/ytaFB245nS8Vin3XQ==
"@redwoodjs/auth@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@redwoodjs/auth/-/auth-0.20.0.tgz#c08c0b735a0b09ef84dc6d357fa2803d61f1d389"
integrity sha512-M1rPtiXzU7YagQ120zEkp5qvJruWRcFLUfBhgexGYgnEkqFIKFGLQL9pBywoZ6kxNjI7x/pbghz6FS7H/lzw/g==
"@redwoodjs/cli@^0.20.0":
version "0.20.0"
@@ -8671,13 +8671,6 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
gotrue-js@^0.9.27:
version "0.9.27"
resolved "https://registry.yarnpkg.com/gotrue-js/-/gotrue-js-0.9.27.tgz#ed01b47e97781f10f26458ff77d83d97bcc65e08"
integrity sha512-XSQ9XGELrnf6bKYaGk+2z1E6aW6+wZ3S6ns3JQz9tXesBuwVPDv/xOTTZ/Qrtu85GJgxTjhJH447w09a73Tneg==
dependencies:
micro-api-client "^3.2.1"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@@ -11338,11 +11331,6 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micro-api-client@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/micro-api-client/-/micro-api-client-3.3.0.tgz#52dd567d322f10faffe63d19d4feeac4e4ffd215"
integrity sha512-y0y6CUB9RLVsy3kfgayU28746QrNMpSm9O/AYGNsBgOkJr/X/Jk0VLGoO8Ude7Bpa8adywzF+MzXNZRFRsNPhg==
microevent.ts@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"