Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
005e866a
Commit
005e866a
authored
Jul 11, 2023
by
JzoNg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: reset password
parent
ddf35c06
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
218 additions
and
73 deletions
+218
-73
index.module.css
web/app/components/header/account-about/index.module.css
+1
-1
index.tsx
web/app/components/header/account-about/index.tsx
+1
-1
index.tsx
.../components/header/account-setting/account-page/index.tsx
+155
-45
index.tsx
web/app/components/header/account-setting/index.tsx
+32
-21
app-context.tsx
web/context/app-context.tsx
+9
-4
common.en.ts
web/i18n/lang/common.en.ts
+9
-0
common.zh.ts
web/i18n/lang/common.zh.ts
+9
-1
common.ts
web/models/common.ts
+2
-0
No files found.
web/app/components/header/account-about/index.module.css
View file @
005e866a
.logo-icon
{
background
:
url(../assets/logo-icon.png)
center
center
no-repeat
;
background-size
:
contain
;
background-size
:
32px
;
box-shadow
:
0px
4px
6px
-1px
rgba
(
0
,
0
,
0
,
0.05
),
0px
2px
4px
-2px
rgba
(
0
,
0
,
0
,
0.05
);
}
...
...
web/app/components/header/account-about/index.tsx
View file @
005e866a
...
...
@@ -34,7 +34,7 @@ export default function AccountAbout({
<
div
>
<
div
className=
{
classNames
(
s
[
'logo-icon'
],
'mx-auto mb-3 w-12 h-12 bg-white rounded border border-gray-200'
,
'mx-auto mb-3 w-12 h-12 bg-white rounded
-xl
border border-gray-200'
,
)
}
/>
<
div
className=
{
classNames
(
s
[
'logo-text'
],
...
...
web/app/components/header/account-setting/account-page/index.tsx
View file @
005e866a
...
...
@@ -25,13 +25,19 @@ const inputClassName = `
text-sm font-normal text-gray-800
`
const
validPassword
=
/^
(?=
.*
[
a-zA-Z
])(?=
.*
\d)
.
{8,}
$/
export
default
function
AccountPage
()
{
const
{
t
}
=
useTranslation
()
const
{
mutateUserProfile
,
userProfile
,
apps
}
=
useAppContext
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
[
editNameModalVisible
,
setEditNameModalVisible
]
=
useState
(
false
)
const
[
editName
,
setEditName
]
=
useState
(
''
)
const
[
editing
,
setEditing
]
=
useState
(
false
)
const
{
t
}
=
useTranslation
()
const
[
editPasswordModalVisible
,
setEditPasswordModalVisible
]
=
useState
(
false
)
const
[
currentPassword
,
setCurrentPassword
]
=
useState
(
''
)
const
[
password
,
setPassword
]
=
useState
(
''
)
const
[
confirmPassword
,
setConfirmPassword
]
=
useState
(
''
)
const
handleEditName
=
()
=>
{
setEditNameModalVisible
(
true
)
...
...
@@ -52,6 +58,56 @@ export default function AccountPage() {
setEditing
(
false
)
}
}
const
showErrorMessage
=
(
message
:
string
)
=>
{
notify
({
type
:
'error'
,
message
,
})
}
const
valid
=
()
=>
{
if
(
!
password
.
trim
())
{
showErrorMessage
(
t
(
'login.error.passwordEmpty'
))
return
false
}
if
(
!
validPassword
.
test
(
password
))
showErrorMessage
(
t
(
'login.error.passwordInvalid'
))
if
(
password
!==
confirmPassword
)
showErrorMessage
(
t
(
'common.account.notEqual'
))
return
true
}
const
resetPasswordForm
=
()
=>
{
setCurrentPassword
(
''
)
setPassword
(
''
)
setConfirmPassword
(
''
)
}
const
handleSavePassowrd
=
async
()
=>
{
if
(
!
valid
())
return
try
{
setEditing
(
true
)
await
updateUserProfile
({
url
:
'account/password'
,
body
:
{
password
:
currentPassword
,
new_password
:
password
,
repeat_new_password
:
confirmPassword
,
},
})
notify
({
type
:
'success'
,
message
:
t
(
'common.actionMsg.modifiedSuccessfully'
)
})
mutateUserProfile
()
setEditPasswordModalVisible
(
false
)
resetPasswordForm
()
setEditing
(
false
)
}
catch
(
e
)
{
notify
({
type
:
'error'
,
message
:
(
e
as
Error
).
message
})
setEditPasswordModalVisible
(
false
)
setEditing
(
false
)
}
}
const
renderAppItem
=
(
item
:
IItem
)
=>
{
return
(
<
div
className=
'flex px-3 py-1'
>
...
...
@@ -80,51 +136,105 @@ export default function AccountPage() {
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.email'
)
}
</
div
>
<
div
className=
{
classNames
(
inputClassName
,
'cursor-pointer'
)
}
>
{
userProfile
.
email
}
</
div
>
</
div
>
{
!!
apps
.
length
&&
(
<>
<
div
className=
'mb-6 border-[0.5px] border-gray-100'
/>
<
div
className=
'mb-8'
>
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.langGeniusAccount'
)
}
</
div
>
<
div
className=
{
descriptionClassName
}
>
{
t
(
'common.account.langGeniusAccountTip'
)
}
</
div
>
<
Collapse
title=
{
`${t('common.account.showAppLength', { length: apps.length })}`
}
items=
{
apps
.
map
(
app
=>
({
key
:
app
.
id
,
name
:
app
.
name
}))
}
renderItem=
{
renderAppItem
}
wrapperClassName=
'mt-2'
/>
</
div
>
</>
)
}
{
editNameModalVisible
&&
(
<
Modal
isShow
onClose=
{
()
=>
setEditNameModalVisible
(
false
)
}
className=
{
s
.
modal
}
>
<
div
className=
'mb-6 text-lg font-medium text-gray-900'
>
{
t
(
'common.account.editName'
)
}
</
div
>
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.name'
)
}
</
div
>
<
input
className=
{
inputClassName
}
value=
{
editName
}
onChange=
{
e
=>
setEditName
(
e
.
target
.
value
)
}
<
div
className=
'mb-8'
>
<
div
className=
'mb-1 text-sm font-medium text-gray-900'
>
{
t
(
'common.account.password'
)
}
</
div
>
<
div
className=
'mb-2 text-xs text-gray-500'
>
{
t
(
'common.account.passwordTip'
)
}
</
div
>
<
Button
className=
'font-medium !text-gray-700 !px-3 !py-[7px] !text-[13px]'
onClick=
{
()
=>
setEditPasswordModalVisible
(
true
)
}
>
{
userProfile
.
is_password_set
?
t
(
'common.account.resetPassword'
)
:
t
(
'common.account.setPassword'
)
}
</
Button
>
</
div
>
{
!!
apps
.
length
&&
(
<>
<
div
className=
'mb-6 border-[0.5px] border-gray-100'
/>
<
div
className=
'mb-8'
>
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.langGeniusAccount'
)
}
</
div
>
<
div
className=
{
descriptionClassName
}
>
{
t
(
'common.account.langGeniusAccountTip'
)
}
</
div
>
<
Collapse
title=
{
`${t('common.account.showAppLength', { length: apps.length })}`
}
items=
{
apps
.
map
(
app
=>
({
key
:
app
.
id
,
name
:
app
.
name
}))
}
renderItem=
{
renderAppItem
}
wrapperClassName=
'mt-2'
/>
<
div
className=
'flex justify-end mt-10'
>
<
Button
className=
'mr-2 text-sm font-medium'
onClick=
{
()
=>
setEditNameModalVisible
(
false
)
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
<
Button
disabled=
{
editing
||
!
editName
}
type=
'primary'
className=
'text-sm font-medium'
onClick=
{
handleSaveName
}
>
{
t
(
'common.operation.save'
)
}
</
Button
>
</
div
>
</
Modal
>
)
}
</
div
>
</>
)
}
{
editNameModalVisible
&&
(
<
Modal
isShow
onClose=
{
()
=>
setEditNameModalVisible
(
false
)
}
className=
{
s
.
modal
}
>
<
div
className=
'mb-6 text-lg font-medium text-gray-900'
>
{
t
(
'common.account.editName'
)
}
</
div
>
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.name'
)
}
</
div
>
<
input
className=
{
inputClassName
}
value=
{
editName
}
onChange=
{
e
=>
setEditName
(
e
.
target
.
value
)
}
/>
<
div
className=
'flex justify-end mt-10'
>
<
Button
className=
'mr-2 text-sm font-medium'
onClick=
{
()
=>
setEditNameModalVisible
(
false
)
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
<
Button
disabled=
{
editing
||
!
editName
}
type=
'primary'
className=
'text-sm font-medium'
onClick=
{
handleSaveName
}
>
{
t
(
'common.operation.save'
)
}
</
Button
>
</
div
>
</
Modal
>
)
}
{
editPasswordModalVisible
&&
(
<
Modal
isShow
onClose=
{
()
=>
{
setEditPasswordModalVisible
(
false
)
resetPasswordForm
()
}
}
className=
{
s
.
modal
}
>
<
div
className=
'mb-6 text-lg font-medium text-gray-900'
>
{
userProfile
.
is_password_set
?
t
(
'common.account.resetPassword'
)
:
t
(
'common.account.setPassword'
)
}
</
div
>
{
userProfile
.
is_password_set
&&
(
<>
<
div
className=
{
titleClassName
}
>
{
t
(
'common.account.currentPassword'
)
}
</
div
>
<
input
type=
"password"
className=
{
inputClassName
}
value=
{
currentPassword
}
onChange=
{
e
=>
setCurrentPassword
(
e
.
target
.
value
)
}
/>
</>
)
}
<
div
className=
'mt-8 text-sm font-medium text-gray-900'
>
{
userProfile
.
is_password_set
?
t
(
'common.account.newPassword'
)
:
t
(
'common.account.password'
)
}
</
div
>
<
input
type=
"password"
className=
{
inputClassName
}
value=
{
password
}
onChange=
{
e
=>
setPassword
(
e
.
target
.
value
)
}
/>
<
div
className=
'mt-8 text-sm font-medium text-gray-900'
>
{
t
(
'common.account.confirmPassword'
)
}
</
div
>
<
input
type=
"password"
className=
{
inputClassName
}
value=
{
confirmPassword
}
onChange=
{
e
=>
setConfirmPassword
(
e
.
target
.
value
)
}
/>
<
div
className=
'flex justify-end mt-10'
>
<
Button
className=
'mr-2 text-sm font-medium'
onClick=
{
()
=>
{
setEditPasswordModalVisible
(
false
)
resetPasswordForm
()
}
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
<
Button
disabled=
{
editing
}
type=
'primary'
className=
'text-sm font-medium'
onClick=
{
handleSavePassowrd
}
>
{
userProfile
.
is_password_set
?
t
(
'common.operation.reset'
)
:
t
(
'common.operation.save'
)
}
</
Button
>
</
div
>
</
Modal
>
)
}
</>
)
}
web/app/components/header/account-setting/index.tsx
View file @
005e866a
'use client'
import
{
useTranslation
}
from
'react-i18next'
import
{
useState
}
from
'react'
import
{
useEffect
,
useRef
,
useState
}
from
'react'
import
cn
from
'classnames'
import
{
AtSymbolIcon
,
CubeTransparentIcon
,
GlobeAltIcon
,
UserIcon
,
UsersIcon
,
XMarkIcon
}
from
'@heroicons/react/24/outline'
import
{
GlobeAltIcon
as
GlobalAltIconSolid
,
UserIcon
as
UserIconSolid
,
UsersIcon
as
UsersIconSolid
}
from
'@heroicons/react/24/solid'
import
AccountPage
from
'./account-page'
...
...
@@ -18,6 +19,10 @@ const iconClassName = `
w-4 h-4 ml-3 mr-2
`
const
scrolledClassName
=
`
border-b shadow-xs bg-white/[.98]
`
type
IAccountSettingProps
=
{
onCancel
:
()
=>
void
activeTab
?:
string
...
...
@@ -78,6 +83,22 @@ export default function AccountSetting({
],
},
]
const
scrollRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
[
scrolled
,
setScrolled
]
=
useState
(
false
)
const
scrollHandle
=
(
e
:
any
)
=>
{
if
(
e
.
target
.
scrollTop
>
0
)
setScrolled
(
true
)
else
setScrolled
(
false
)
}
useEffect
(()
=>
{
const
targetElement
=
scrollRef
.
current
targetElement
?.
addEventListener
(
'scroll'
,
scrollHandle
)
return
()
=>
{
targetElement
?.
removeEventListener
(
'scroll'
,
scrollHandle
)
}
},
[])
return
(
<
Modal
...
...
@@ -115,29 +136,19 @@ export default function AccountSetting({
}
</
div
>
</
div
>
<
div
className=
'w-[520px] h-[580px] px-6 py
-4 overflow-y-auto'
>
<
div
className=
'flex items-center justify-between h-6 mb-8 text-base font-medium text-gray-900 '
>
<
div
ref=
{
scrollRef
}
className=
'relative w-[520px] h-[580px] pb
-4 overflow-y-auto'
>
<
div
className=
{
cn
(
'sticky top-0 px-6 py-4 flex items-center justify-between h-14 mb-4 bg-white text-base font-medium text-gray-900'
,
scrolled
&&
scrolledClassName
)
}
>
{
[...
menuItems
[
0
].
items
,
...
menuItems
[
1
].
items
].
find
(
item
=>
item
.
key
===
activeMenu
)?.
name
}
<
XMarkIcon
className=
'w-4 h-4 cursor-pointer'
onClick=
{
onCancel
}
/>
</
div
>
{
activeMenu
===
'account'
&&
<
AccountPage
/>
}
{
activeMenu
===
'members'
&&
<
MembersPage
/>
}
{
activeMenu
===
'integrations'
&&
<
IntegrationsPage
/>
}
{
activeMenu
===
'language'
&&
<
LanguagePage
/>
}
{
activeMenu
===
'provider'
&&
<
ProviderPage
/>
}
{
activeMenu
===
'data-source'
&&
<
DataSourcePage
/>
}
<
div
className=
'px-6'
>
{
activeMenu
===
'account'
&&
<
AccountPage
/>
}
{
activeMenu
===
'members'
&&
<
MembersPage
/>
}
{
activeMenu
===
'integrations'
&&
<
IntegrationsPage
/>
}
{
activeMenu
===
'language'
&&
<
LanguagePage
/>
}
{
activeMenu
===
'provider'
&&
<
ProviderPage
/>
}
{
activeMenu
===
'data-source'
&&
<
DataSourcePage
/>
}
</
div
>
</
div
>
</
div
>
</
Modal
>
...
...
web/context/app-context.tsx
View file @
005e866a
'use client'
import
{
createContext
,
useContext
,
useContextSelector
}
from
'use-context-selector'
import
type
{
FC
,
PropsWithChildren
}
from
'react'
import
{
createRef
}
from
'react'
import
type
{
App
}
from
'@/types/app'
import
type
{
UserProfileResponse
}
from
'@/models/common'
import
{
createRef
,
FC
,
PropsWithChildren
}
from
'react'
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export
const
useSelector
=
<
T
extends
any
>
(selector: (value: AppContextValue) =
>
T): T =
>
useContextSelector(AppContext, selector);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
useContextSelector(AppContext, selector)
export type AppContextValue =
{
apps
:
App
[]
mutateApps
:
()
=>
void
userProfile
:
UserProfileResponse
mutateUserProfile
:
()
=>
void
pageContainerRef
:
React
.
RefObject
<
HTMLDivElement
>
,
useSelector
:
typeof
useSelector
,
pageContainerRef
:
React
.
RefObject
<
HTMLDivElement
>
useSelector
:
typeof
useSelector
}
const AppContext = createContext
<
AppContextValue
>
(
{
...
...
@@ -24,6 +27,8 @@ const AppContext = createContext<AppContextValue>({
id
:
''
,
name
:
''
,
email
:
''
,
avatar
:
''
,
is_password_set
:
false
,
},
mutateUserProfile
:
()
=>
{
},
pageContainerRef
:
createRef
(),
...
...
web/i18n/lang/common.en.ts
View file @
005e866a
...
...
@@ -14,6 +14,7 @@ const translation = {
edit
:
'Edit'
,
add
:
'Add'
,
refresh
:
'Restart'
,
reset
:
'Reset'
,
search
:
'Search'
,
change
:
'Change'
,
remove
:
'Remove'
,
...
...
@@ -93,6 +94,14 @@ const translation = {
avatar
:
'Avatar'
,
name
:
'Name'
,
email
:
'Email'
,
password
:
'Password'
,
passwordTip
:
'You can set a permanent password if you don’t want to use temporary login codes'
,
setPassword
:
'Set a password'
,
resetPassword
:
'Reset password'
,
currentPassword
:
'Current password'
,
newPassword
:
'New password'
,
confirmPassword
:
'Confirm password'
,
notEqual
:
'Two passwords are different.'
,
langGeniusAccount
:
'Dify account'
,
langGeniusAccountTip
:
'Your Dify account and associated user data.'
,
editName
:
'Edit Name'
,
...
...
web/i18n/lang/common.zh.ts
View file @
005e866a
...
...
@@ -14,6 +14,7 @@ const translation = {
edit
:
'编辑'
,
add
:
'添加'
,
refresh
:
'重新开始'
,
reset
:
'重置'
,
search
:
'搜索'
,
change
:
'更改'
,
remove
:
'移除'
,
...
...
@@ -93,7 +94,14 @@ const translation = {
avatar
:
'头像'
,
name
:
'用户名'
,
email
:
'邮箱'
,
edit
:
'编辑'
,
password
:
'密码'
,
passwordTip
:
'如果您不想使用验证码登录,可以设置永久密码'
,
setPassword
:
'设置密码'
,
resetPassword
:
'重置密码'
,
currentPassword
:
'原密码'
,
newPassword
:
'新密码'
,
notEqual
:
'两个密码不相同'
,
confirmPassword
:
'确认密码'
,
langGeniusAccount
:
'Dify 账号'
,
langGeniusAccountTip
:
'您的 Dify 账号和相关的用户数据。'
,
editName
:
'编辑名字'
,
...
...
web/models/common.ts
View file @
005e866a
...
...
@@ -10,6 +10,8 @@ export type UserProfileResponse = {
id
:
string
name
:
string
email
:
string
avatar
:
string
is_password_set
:
boolean
interface_language
?:
string
interface_theme
?:
string
timezone
?:
string
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment