issue-129 Update signin/up ui-ux #136
@@ -62,12 +62,12 @@ export const handler = async (req, _context) => {
|
||||
db.user.findOne({
|
||||
where: { userName: seed },
|
||||
})
|
||||
const userNameSeed = enforceAlphaNumeric(email.split('@')[0])
|
||||
const userNameSeed = enforceAlphaNumeric(user?.user_metadata?.userName)
|
||||
const userName = await generateUniqueString(userNameSeed, isUniqueCallback) // TODO maybe come up with a better default userName?
|
||||
const input = {
|
||||
email,
|
||||
userName,
|
||||
name: user.user_metadata && user.user_metadata.full_name,
|
||||
name: user?.user_metadata?.full_name,
|
||||
id: user.id,
|
||||
}
|
||||
await createUserInsecure({ input })
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@redwoodjs/auth": "^0.20.0",
|
||||
"@redwoodjs/auth": "^0.21.0",
|
||||
"@redwoodjs/forms": "^0.20.0",
|
||||
"@redwoodjs/router": "^0.20.0",
|
||||
"@redwoodjs/web": "^0.20.0",
|
||||
@@ -22,6 +22,7 @@
|
||||
"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",
|
||||
|
||||
@@ -32,6 +32,8 @@ const Routes = () => {
|
||||
)
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/account-recovery/update-password" page={UpdatePasswordPage} name="updatePassword" />
|
||||
<Route path="/account-recovery" page={AccountRecoveryPage} name="accountRecovery" />
|
||||
<Route path="/" page={PartsPage} name="home" />
|
||||
<Route notfound page={NotFoundPage} />
|
||||
|
||||
|
||||
34
web/src/components/InputTextForm/InputTextForm.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { getActiveClasses } from 'get-active-classes'
|
||||
import { TextField, FieldError } from '@redwoodjs/forms'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
import InputTextForm from './InputTextForm'
|
||||
|
||||
export const generated = () => {
|
||||
return <InputTextForm />
|
||||
}
|
||||
|
||||
export default { title: 'Components/InputTextForm' }
|
||||
11
web/src/components/InputTextForm/InputTextForm.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import InputTextForm from './InputTextForm'
|
||||
|
||||
describe('InputTextForm', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<InputTextForm />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -7,17 +7,18 @@ 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 { logIn } = useAuth()
|
||||
const recordedLogin = () => {
|
||||
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false)
|
||||
const recordedLogin = async () => {
|
||||
ReactGA.event({
|
||||
category: 'login',
|
||||
action: 'landing section CTA',
|
||||
})
|
||||
logIn()
|
||||
setIsLoginModalOpen(true)
|
||||
}
|
||||
return (
|
||||
<div className="mt-16">
|
||||
@@ -181,6 +182,11 @@ const LandingSection = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LoginModal
|
||||
open={isLoginModalOpen}
|
||||
onClose={() => setIsLoginModalOpen(false)}
|
||||
shouldStartWithSignup
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
189
web/src/components/LoginModal/LoginModal.js
Normal file
@@ -0,0 +1,189 @@
|
||||
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 { useFlash } from '@redwoodjs/web'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import { subscribe } from 'src/helpers/subscribe'
|
||||
|
||||
const LoginModal = ({ open, onClose, shouldStartWithSignup = false }) => {
|
||||
|
|
||||
const { logIn, signUp } = useAuth()
|
||||
const { addMessage } = useFlash()
|
||||
|
||||
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('')
|
||||
if (checkBox) {
|
||||
subscribe({ email, addMessage })
|
||||
|
Somewhat crude way of subscribing the user to the newsletter, this doesn't got through the API like everything else so it's a little out place. But it works for now. Somewhat crude way of subscribing the user to the newsletter, this doesn't got through the API like everything else so it's a little out place. But it works for now.
|
||||
}
|
||||
await signUp({
|
||||
email,
|
||||
password,
|
||||
remember: { full_name: name, userName },
|
||||
|
Any extra data that you want be inside the payload that's passed to the Why is the data put on a property called remember? I'm not sure but it's pretty confusing since for Redwood has in mostly places changed gotrue's ordered arguments to named object arguments. Any extra data that you want be inside the payload that's passed to the `identity-signup` netlify function needs to be added to this remember property. It will be in the `user_metadata` property in the payload.
I'm opting to continue using `full_name` for the name as this will display nicely as `Name` in the netlify identity dashboard

Why is the data put on a property called remember? I'm not sure but it's pretty confusing since for `login`, `remember` is a boolean value that's mean for persisting the users log in state.
Redwood has in mostly places changed gotrue's ordered arguments to named object arguments.

https://github.com/netlify/gotrue-js/blob/d1565a7d0576ff613b1c37c46a42d1fcbd720c7c/index.d.ts
Which I'd normally like, but with the name change it becomes confusing figuring how things differ from gotrue's docs, this might be side-effect of integrating with multiple auth providers and trying to unify the API for all of them, which I can't fault. I just got caught on this for a bit.
|
||||
})
|
||||
onClose()
|
||||
} catch (errorEvent) {
|
||||
setError(errorEvent?.json?.error_description)
|
||||
}
|
||||
}
|
||||
const onSubmitSignIn = async ({ email, password }) => {
|
||||
try {
|
||||
setError('')
|
||||
await logIn({ email, password, remember: true })
|
||||
|
As above mentioned above, As above mentioned above, `remember` here has to do with persisting the login.
|
||||
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
|
||||
7
web/src/components/LoginModal/LoginModal.stories.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import LoginModal from './LoginModal'
|
||||
|
||||
export const generated = () => {
|
||||
return <LoginModal open={true} />
|
||||
}
|
||||
|
||||
export default { title: 'Components/LoginModal' }
|
||||
11
web/src/components/LoginModal/LoginModal.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import LoginModal from './LoginModal'
|
||||
|
||||
describe('LoginModal', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<LoginModal />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
15
web/src/helpers/subscribe.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const subscribe = ({ email, addMessage }) => {
|
||||
// subscribe to mailchimp newsletter
|
||||
const path = window.location.hostname + window.location.pathname
|
||||
try {
|
||||
fetch(
|
||||
`https://kurthutten.us10.list-manage.com/subscribe/post-json?u=cbd8888e924bdd99d06c14fa5&id=6a765a8b3d&EMAIL=${email}&FNAME=Kurt&PATHNAME=${path}&c=__jp0`
|
||||
)
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
addMessage('Problem subscribing to newsletter', {
|
||||
classes: 'bg-red-300 text-red-900',
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AuthProvider } from '@redwoodjs/auth'
|
||||
import netlifyIdentity from 'netlify-identity-widget'
|
||||
import GoTrue from 'gotrue-js'
|
||||
|
||||
import ReactDOM from 'react-dom'
|
||||
import { RedwoodProvider, FatalErrorBoundary } from '@redwoodjs/web'
|
||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||
@@ -16,8 +17,6 @@ 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) {
|
||||
@@ -49,9 +48,14 @@ function initCascadeStudio() {
|
||||
}
|
||||
initCascadeStudio()
|
||||
|
||||
const goTrueClient = new GoTrue({
|
||||
APIUrl: 'https://cadhub.xyz/.netlify/identity',
|
||||
setCookie: true,
|
||||
})
|
||||
|
||||
ReactDOM.render(
|
||||
<FatalErrorBoundary page={FatalErrorPage}>
|
||||
<AuthProvider client={netlifyIdentity} type="netlify">
|
||||
<AuthProvider client={goTrueClient} type="goTrue">
|
||||
<RedwoodProvider>
|
||||
<Routes />
|
||||
</RedwoodProvider>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import { Link, routes, navigate } from '@redwoodjs/router'
|
||||
import { useAuth } from '@redwoodjs/auth'
|
||||
import { Flash } from '@redwoodjs/web'
|
||||
import { Flash, useQuery, useFlash } 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,11 +26,13 @@ let previousSubmission = ''
|
||||
let previousUserID = ''
|
||||
|
||||
const MainLayout = ({ children }) => {
|
||||
const { logIn, logOut, isAuthenticated, currentUser } = useAuth()
|
||||
const { logOut, isAuthenticated, currentUser, client } = useAuth()
|
||||
const { addMessage } = useFlash()
|
||||
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)
|
||||
@@ -59,7 +61,7 @@ const MainLayout = ({ children }) => {
|
||||
category: 'login',
|
||||
action: 'navbar login',
|
||||
})
|
||||
logIn()
|
||||
setIsLoginModalOpen(true)
|
||||
}
|
||||
|
||||
const { pathname, params } = useLocation()
|
||||
@@ -85,6 +87,34 @@ 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') {
|
||||
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">
|
||||
@@ -198,7 +228,11 @@ const MainLayout = ({ children }) => {
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
<Flash timeout={1000} />
|
||||
<Flash timeout={1500} />
|
||||
<LoginModal
|
||||
open={isLoginModalOpen}
|
||||
onClose={() => setIsLoginModalOpen(false)}
|
||||
/>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
|
||||
67
web/src/pages/AccountRecoveryPage/AccountRecoveryPage.js
Normal file
@@ -0,0 +1,67 @@
|
||||
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)
|
||||
|
Send recovery email. Send recovery 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
|
||||
@@ -0,0 +1,7 @@
|
||||
import AccountRecoveryPage from './AccountRecoveryPage'
|
||||
|
||||
export const generated = () => {
|
||||
return <AccountRecoveryPage />
|
||||
}
|
||||
|
||||
export default { title: 'Pages/AccountRecoveryPage' }
|
||||
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import AccountRecoveryPage from './AccountRecoveryPage'
|
||||
|
||||
describe('AccountRecoveryPage', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<AccountRecoveryPage />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
78
web/src/pages/UpdatePasswordPage/UpdatePasswordPage.js
Normal file
@@ -0,0 +1,78 @@
|
||||
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 || !password) {
|
||||
addMessage("Passwords don't match, try again", {
|
||||
classes: 'bg-red-300 text-red-900',
|
||||
})
|
||||
return
|
||||
}
|
||||
client
|
||||
.currentUser()
|
||||
.update({ password })
|
||||
|
This update function can be used to update any property on the user. Though, for email (I haven't built email update UI), but if you plan to it's worth knowing there is an undocumented token needed This update function can be used to update any property on the user. Though, for email (I haven't built email update UI), but if you plan to it's worth knowing there is an [undocumented token needed](https://github.com/netlify/gotrue-js/issues/61)
|
||||
.then(() => {
|
||||
addMessage('Password updated', { classes: 'rw-flash-success' })
|
||||
setTimeout(() => {
|
||||
navigate(routes.home())
|
||||
}, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
addMessage('Problem updating password', {
|
||||
classes: 'bg-red-300 text-red-900',
|
||||
})
|
||||
})
|
||||
}
|
||||
return (
|
||||
<MainLayout>
|
||||
<Seo title="Update Password" description="Update Password" 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="password"
|
||||
type="password"
|
||||
validation={{
|
||||
required: true,
|
||||
}}
|
||||
/>
|
||||
<span className="capitalize text-gray-500 text-sm align-middle my-3">
|
||||
confirm:
|
||||
</span>
|
||||
<InputTextForm
|
||||
className="text-xl"
|
||||
name="confirm"
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
import UpdatePasswordPage from './UpdatePasswordPage'
|
||||
|
||||
export const generated = () => {
|
||||
return <UpdatePasswordPage />
|
||||
}
|
||||
|
||||
export default { title: 'Pages/UpdatePasswordPage' }
|
||||
11
web/src/pages/UpdatePasswordPage/UpdatePasswordPage.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import UpdatePasswordPage from './UpdatePasswordPage'
|
||||
|
||||
describe('UpdatePasswordPage', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<UpdatePasswordPage />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
20
yarn.lock
@@ -2366,10 +2366,10 @@
|
||||
lodash.omitby "^4.6.0"
|
||||
merge-graphql-schemas "^1.7.6"
|
||||
|
||||
"@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/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/cli@^0.20.0":
|
||||
version "0.20.0"
|
||||
@@ -8671,6 +8671,13 @@ 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"
|
||||
@@ -11331,6 +11338,11 @@ 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"
|
||||
|
||||



Very light abstraction of individual text input that still working

with react-hoo-forms.