Skip to content

Commit

Permalink
Add profile/user information component
Browse files Browse the repository at this point in the history
  • Loading branch information
mpgxvii committed Jul 19, 2024
1 parent bedd22d commit 270f4d6
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 0 deletions.
23 changes: 23 additions & 0 deletions data/profile-questionnaire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const profileQuestions = [
{
field_name: "first_name",
form_name: "profile",
field_type: "text",
field_label: "First Name",
required_field: "yes",
},
{
field_name: "last_name",
form_name: "profile",
field_type: "text",
field_label: "Last Name",
required_field: "yes",
},
{
field_name: "address",
form_name: "profile",
field_type: "text",
field_label: "Address",
required_field: "yes",
},
]
6 changes: 6 additions & 0 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ const Home: NextPage = () => {
title="Study Consent"
disabled={!hasSession}
/>
<DocsButton
testid="profile"
href="/profile"
title="Profile"
disabled={!hasSession}
/>
<DocsButton
testid="account-settings"
href="/settings"
Expand Down
191 changes: 191 additions & 0 deletions pages/profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { SettingsFlow } from "@ory/client"
import { AxiosError } from "axios"
import type { NextPage } from "next"
import Head from "next/head"
import Link from "next/link"
import { useRouter } from "next/router"
import { ReactNode, useEffect, useState } from "react"
import { profileQuestions } from "../data/profile-questionnaire"

import {
ActionCard,
CenterLink,
Flow,
Messages,
Methods,
CardTitle,
InnerCard,
} from "../pkg"
import { handleFlowError } from "../pkg/errors"
import ory from "../pkg/sdk"

interface Props {
flow?: SettingsFlow
only?: Methods
}

function ProfileCard({
flow,
only,
children,
}: Props & { children: ReactNode }) {
return <ActionCard wide>{children}</ActionCard>
}

const Profile: NextPage = () => {
const [flow, setFlow] = useState<SettingsFlow>()

// Get ?flow=... from the URL
const router = useRouter()
const { flow: flowId, return_to: returnTo } = router.query
const [csrfToken, setCsrfToken] = useState<string>("")
const [traits, setTraits] = useState<any>()
const [profile, setProfile] = useState<any>({})

useEffect(() => {
// If the router is not ready yet, or we already have a flow, do nothing.
if (!router.isReady || flow) {
return
}

// If ?flow=.. was in the URL, we fetch it
if (flowId) {
ory
.getSettingsFlow({ id: String(flowId) })
.then(({ data }) => {
setFlow(data)
})
.catch(handleFlowError(router, "settings", setFlow))
return
}

// Otherwise we initialize it
ory
.createBrowserSettingsFlow({
returnTo: String(returnTo || ""),
})
.then(({ data }) => {
setFlow(data)
const csrfTokenFromHeaders = data.ui.nodes.find(
(node: any) => node.attributes.name === "csrf_token",
)?.attributes.value
const traits = data.identity.traits
setCsrfToken(csrfTokenFromHeaders || "")
setTraits(traits)
setProfile(traits.additional_information || {})
})
.catch(handleFlowError(router, "settings", setFlow))
}, [flowId, router, router.isReady, returnTo, flow])

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setProfile({
...profile,
[event.target.name]: event.target.value,
})
}

const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const updatedValues = {
csrf_token: csrfToken,
method: "profile",
traits: {
...traits,
additional_information: profile,
},
}

return (
router
// On submission, add the flow ID to the URL but do not navigate. This prevents the user losing
// his data when she/he reloads the page.
.push(`/`)
.then(() =>
ory
.updateSettingsFlow({
flow: String(flow?.id),
updateSettingsFlowBody: updatedValues,
})
.then(({ data }) => {
// The settings have been saved and the flow was updated. Let's show it to the user!
setFlow(data)

if (data.return_to) {
window.location.href = data.return_to
return
}
})
.catch(handleFlowError(router, "profile", setFlow))
.catch(async (err: AxiosError) => {
// If the previous handler did not catch the error it's most likely a form validation error
if (err.response?.status === 400) {
// Yup, it is!
setFlow(err.response?.data)
return
}

return Promise.reject(err)
}),
)
)
}

return (
<>
<Head>
<title>Profile Page</title>
<meta name="description" content="NextJS + React + Vercel + Ory" />
</Head>
<CardTitle style={{ marginTop: 80 }}></CardTitle>
<ProfileCard only="profile" flow={flow}>
<CardTitle>User Information</CardTitle>
<ProfileForm
questions={profileQuestions}
onSubmit={onSubmit}
handleChange={handleChange}
profile={profile}
/>
</ProfileCard>
<ActionCard wide>
<Link href="/" passHref>
<CenterLink>Go back</CenterLink>
</Link>
</ActionCard>
</>
)
}

const ProfileForm: React.FC<any> = ({
questions,
onSubmit,
handleChange,
profile,
}) => {
return (
<form method="POST" onSubmit={onSubmit}>
{questions.map((question, index) => {
if (question.field_type === "text") {
return (
<div key={index}>
<label className="inputLabel">{question.field_label}</label>
<input
className="input"
id={question.field_name}
name={question.field_name}
type="text"
value={profile[question.field_name] || ""}
onChange={handleChange}
required={question.required_field === "yes"}
/>
</div>
)
}
return null
})}
<br />
<button type="submit">Save</button>
</form>
)
}

export default Profile
3 changes: 3 additions & 0 deletions styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ button,
color: black;
padding: 8px;
}
span {
font-size: 12px;
}
}

input {
Expand Down

0 comments on commit 270f4d6

Please sign in to comment.