[FWA-3] completed update Profile
This commit is contained in:
@ -2,3 +2,4 @@ export const POST_LOGIN = '/api/auth/login'
|
||||
export const POST_LOGOUT = '/api/auth/logout'
|
||||
export const POST_REFRESH = '/api/auth/refresh'
|
||||
export const GET_USER_PROFILE = '/api/user/me'
|
||||
export const PUT_UPDATE_USER_PROFILE = '/api/user/update-profile'
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { protocol } from './index'
|
||||
import { GET_USER_PROFILE } from './url'
|
||||
import { GET_USER_PROFILE, PUT_UPDATE_USER_PROFILE } from './url'
|
||||
|
||||
export const getProfile = () => {
|
||||
return protocol.get(GET_USER_PROFILE, {})
|
||||
}
|
||||
|
||||
export const putUpdateProfile = (payload) => {
|
||||
return protocol.put(PUT_UPDATE_USER_PROFILE, payload)
|
||||
}
|
||||
|
39
frontend/src/components/common/TextInput/TextInput.jsx
Normal file
39
frontend/src/components/common/TextInput/TextInput.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Field } from 'solid-form-handler'
|
||||
import { Show, splitProps } from 'solid-js'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
|
||||
export default function Finput(props) {
|
||||
const [local, rest] = splitProps(props, ['label', 'icon'])
|
||||
|
||||
return (
|
||||
<Field
|
||||
{...props}
|
||||
mode="input"
|
||||
render={(field) => (
|
||||
<div class="form-control w-full [&:not(:last-child)]:pb-3">
|
||||
<Show when={local.label}>
|
||||
<div class="label">
|
||||
<span class="label-text">{local.label}</span>
|
||||
</div>
|
||||
</Show>
|
||||
<label
|
||||
class="input input-bordered flex items-center gap-2 w-full"
|
||||
classList={{ 'input-error': field.helpers.error }}
|
||||
>
|
||||
<Show when={local.icon}>
|
||||
<Dynamic component={local.icon} size={18} />
|
||||
</Show>
|
||||
<input {...rest} class="grow w-full" {...field.props} />
|
||||
</label>
|
||||
<Show when={field.helpers.error}>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-red-600">
|
||||
{field.helpers.errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
1
frontend/src/components/common/TextInput/index.js
Normal file
1
frontend/src/components/common/TextInput/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './TextInput'
|
@ -7,12 +7,7 @@ export default function useAuth(setAuth) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const clickLogIn = async (username, password, cbFormReset) => {
|
||||
const loginData = {
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
const resp = await postLogin(loginData)
|
||||
const resp = await postLogin({ username, password })
|
||||
|
||||
if (resp.status === 200) {
|
||||
const token = resp.data || {}
|
||||
|
@ -6,7 +6,13 @@
|
||||
"logout": "Đăng xuất",
|
||||
"dashboard": "Bảng điều khiển",
|
||||
"profile": "Hồ sơ",
|
||||
"houses": "Kho"
|
||||
"changeInfo": "Đổi thông tin",
|
||||
"save": "Lưu",
|
||||
"clear": "Xóa",
|
||||
"houses": "Kho",
|
||||
"displayName": "Display Name",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password"
|
||||
},
|
||||
"message": {
|
||||
"CREATED_USER": "Username already registered!",
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { Field, useFormHandler } from 'solid-form-handler'
|
||||
import { IconKey, IconUser } from '@tabler/icons-solidjs'
|
||||
import { useFormHandler } from 'solid-form-handler'
|
||||
import { yupSchema } from 'solid-form-handler/yup'
|
||||
import { Show, onMount } from 'solid-js'
|
||||
import { onMount } from 'solid-js'
|
||||
import * as yup from 'yup'
|
||||
import './login.scss'
|
||||
|
||||
import Logo from '@assets/logo.svg'
|
||||
import TextInput from '@components/common/TextInput'
|
||||
import useAuth from '@hooks/useAuth'
|
||||
import useToast from '@hooks/useToast'
|
||||
|
||||
@ -66,79 +68,18 @@ export default function Login() {
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{language.ui.login}</h1>
|
||||
<form autoComplete="off" onSubmit={submit}>
|
||||
<Field
|
||||
mode="input"
|
||||
<TextInput
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
icon={IconUser}
|
||||
formHandler={formHandler}
|
||||
render={(field) => (
|
||||
<label class="form-control w-full pb-5">
|
||||
<label
|
||||
class="input input-bordered flex items-center gap-2 w-full"
|
||||
classList={{ 'input-error': field.helpers.error }}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4 opacity-70"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
|
||||
</svg>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
class="grow w-full"
|
||||
placeholder="Username"
|
||||
{...field.props}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Show when={field.helpers.error}>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-red-600">
|
||||
{field.helpers.errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</label>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
mode="input"
|
||||
<TextInput
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
icon={IconKey}
|
||||
formHandler={formHandler}
|
||||
render={(field) => (
|
||||
<label class="form-control w-full">
|
||||
<label
|
||||
class="input input-bordered flex items-center gap-2 w-full"
|
||||
classList={{ 'input-error': field.helpers.error }}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4 opacity-70"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
|
||||
</svg>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
class="grow w-full"
|
||||
placeholder="Password"
|
||||
{...field.props}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Show when={field.helpers.error}>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-red-600">
|
||||
{field.helpers.errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</label>
|
||||
)}
|
||||
/>
|
||||
<div class="card-actions justify-end mt-5">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function Profile() {
|
||||
return <>Profile</>
|
||||
}
|
116
frontend/src/pages/Profile/Profile.jsx
Normal file
116
frontend/src/pages/Profile/Profile.jsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { putUpdateProfile } from '@api/user'
|
||||
import TextInput from '@components/common/TextInput'
|
||||
import { useSiteContext } from '@context/SiteContext'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import useToast from '@hooks/useToast'
|
||||
import { IconKey, IconLetterN, IconUserCircle } from '@tabler/icons-solidjs'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { useFormHandler } from 'solid-form-handler'
|
||||
import { yupSchema } from 'solid-form-handler/yup'
|
||||
import { createEffect } from 'solid-js'
|
||||
import * as yup from 'yup'
|
||||
|
||||
const profileSchema = yup.object({
|
||||
name: yup.string().required('Name is required'),
|
||||
password: yup.string().nullable().optional(),
|
||||
'confirm-password': yup.string().when('password', {
|
||||
is: (val) => !!(val && val.length > 0),
|
||||
then: (schema) =>
|
||||
schema.oneOf([yup.ref('password'), null], 'Passwords must match'),
|
||||
}),
|
||||
})
|
||||
|
||||
const language = useLanguage()
|
||||
const notify = useToast()
|
||||
|
||||
export default function Profile() {
|
||||
const {
|
||||
store: { userInfo },
|
||||
setUser,
|
||||
} = useSiteContext()
|
||||
const formHandler = useFormHandler(yupSchema(profileSchema))
|
||||
const { formData } = formHandler
|
||||
|
||||
createEffect(() => {
|
||||
formHandler.fillForm(
|
||||
{
|
||||
name: userInfo?.name,
|
||||
},
|
||||
{ silentValidation: false },
|
||||
)
|
||||
})
|
||||
|
||||
const submit = async (event) => {
|
||||
event.preventDefault()
|
||||
await formHandler.validateForm()
|
||||
try {
|
||||
const { name, password } = formData()
|
||||
const clearObj = Helpers.clearObject({
|
||||
name: name || null,
|
||||
password: password || null,
|
||||
})
|
||||
const resp = await putUpdateProfile(clearObj)
|
||||
|
||||
if (resp.status === 200) {
|
||||
setUser(resp.data)
|
||||
notify.success({
|
||||
title: 'Update profile success!',
|
||||
description: 'Your profile has been updated!',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
notify.error({
|
||||
title: 'Update profile fail!',
|
||||
description: error?.data
|
||||
? language.message[error.data]
|
||||
: 'Your username or password input is wrong!',
|
||||
closable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="profile">
|
||||
<div class="divider divider-accent text-xl">
|
||||
<span class="text-green-400">
|
||||
<IconUserCircle size={30} />
|
||||
</span>
|
||||
{language.ui.profile}
|
||||
</div>
|
||||
<div class="card w-full bg-base-100 shadow-lg border border-gray-200">
|
||||
<div class="card-body">
|
||||
<form autoComplete="off" onSubmit={submit}>
|
||||
<p class="card-title">{language.ui.changeInfo}</p>
|
||||
<div class="form-content py-5">
|
||||
<TextInput
|
||||
icon={IconLetterN}
|
||||
name="name"
|
||||
placeholder={language.ui.displayName}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<TextInput
|
||||
icon={IconKey}
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={language.ui.newPassword}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
<TextInput
|
||||
icon={IconKey}
|
||||
name="confirm-password"
|
||||
type="password"
|
||||
placeholder={language.ui.confirmNewPassword}
|
||||
formHandler={formHandler}
|
||||
/>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.save}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
1
frontend/src/pages/Profile/index.js
Normal file
1
frontend/src/pages/Profile/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Profile'
|
@ -47,4 +47,13 @@ export class Helpers {
|
||||
? JSON.parse(AES.decrypt(hash, SECRET_KEY).toString(enc.Utf8))
|
||||
: defaultValue
|
||||
}
|
||||
|
||||
static clearObject = (object) => {
|
||||
for (var propName in object) {
|
||||
if (object[propName] === null || object[propName] === undefined) {
|
||||
delete object[propName]
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user