Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
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
Vũ Hoàng Anh
canifa_note
Commits
0dbc35a2
Commit
0dbc35a2
authored
Feb 02, 2026
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: restore access token creation flow
Fixes #5563
parent
e1c8101d
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
93 additions
and
37 deletions
+93
-37
CreateAccessTokenDialog.tsx
web/src/components/CreateAccessTokenDialog.tsx
+75
-33
AccessTokenSection.tsx
web/src/components/Settings/AccessTokenSection.tsx
+18
-4
No files found.
web/src/components/CreateAccessTokenDialog.tsx
View file @
0dbc35a2
import
React
,
{
useState
}
from
"react"
;
import
copy
from
"copy-to-clipboard"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Dialog
,
DialogContent
,
DialogFooter
,
DialogHeader
,
DialogTitle
}
from
"@/components/ui/dialog"
;
import
{
Dialog
,
DialogContent
,
DialogFooter
,
DialogHeader
,
DialogTitle
}
from
"@/components/ui/dialog"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
RadioGroup
,
RadioGroupItem
}
from
"@/components/ui/radio-group"
;
import
{
RadioGroup
,
RadioGroupItem
}
from
"@/components/ui/radio-group"
;
import
{
Textarea
}
from
"@/components/ui/textarea"
;
import
{
userServiceClient
}
from
"@/connect"
;
import
{
userServiceClient
}
from
"@/connect"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useLoading
from
"@/hooks/useLoading"
;
...
@@ -30,6 +32,7 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
...
@@ -30,6 +32,7 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
description
:
""
,
description
:
""
,
expiration
:
30
,
// Default: 30 days
expiration
:
30
,
// Default: 30 days
});
});
const
[
createdToken
,
setCreatedToken
]
=
useState
<
string
|
null
>
(
null
);
const
requestState
=
useLoading
(
false
);
const
requestState
=
useLoading
(
false
);
// Expiration options in days (0 = never expires)
// Expiration options in days (0 = never expires)
...
@@ -83,7 +86,11 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
...
@@ -83,7 +86,11 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
requestState
.
setFinish
();
requestState
.
setFinish
();
onSuccess
(
response
);
onSuccess
(
response
);
onOpenChange
(
false
);
if
(
response
.
token
)
{
setCreatedToken
(
response
.
token
);
}
else
{
onOpenChange
(
false
);
}
}
catch
(
error
:
unknown
)
{
}
catch
(
error
:
unknown
)
{
handleError
(
error
,
toast
.
error
,
{
handleError
(
error
,
toast
.
error
,
{
context
:
"Create access token"
,
context
:
"Create access token"
,
...
@@ -92,46 +99,81 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
...
@@ -92,46 +99,81 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
}
}
};
};
const
handleCopyToken
=
()
=>
{
if
(
!
createdToken
)
return
;
copy
(
createdToken
);
toast
.
success
(
t
(
"message.copied"
));
};
useEffect
(()
=>
{
if
(
!
open
)
return
;
setState
({
description
:
""
,
expiration
:
30
,
});
setCreatedToken
(
null
);
},
[
open
]);
return
(
return
(
<
Dialog
open=
{
open
}
onOpenChange=
{
onOpenChange
}
>
<
Dialog
open=
{
open
}
onOpenChange=
{
onOpenChange
}
>
<
DialogContent
className=
"max-w-md"
>
<
DialogContent
className=
"max-w-md"
>
<
DialogHeader
>
<
DialogHeader
>
<
DialogTitle
>
{
t
(
"setting.access-token-section.create-dialog.create-access-token"
)
}
</
DialogTitle
>
<
DialogTitle
>
{
t
(
"setting.access-token-section.create-dialog.create-access-token"
)
}
</
DialogTitle
>
</
DialogHeader
>
</
DialogHeader
>
<
div
className=
"flex flex-col gap-4"
>
{
createdToken
?
(
<
div
className=
"grid gap-2"
>
<
div
className=
"flex flex-col gap-4"
>
<
Label
htmlFor=
"description"
>
<
div
className=
"grid gap-2"
>
{
t
(
"setting.access-token-section.create-dialog.description"
)
}
<
span
className=
"text-destructive"
>
*
</
span
>
<
Label
>
{
t
(
"setting.access-token-section.token"
)
}
</
Label
>
</
Label
>
<
Textarea
value=
{
createdToken
}
readOnly
rows=
{
3
}
className=
"font-mono text-xs"
/>
<
Input
</
div
>
id=
"description"
type=
"text"
placeholder=
{
t
(
"setting.access-token-section.create-dialog.some-description"
)
}
value=
{
state
.
description
}
onChange=
{
handleDescriptionInputChange
}
/>
</
div
>
</
div
>
<
div
className=
"grid gap-2"
>
)
:
(
<
Label
>
<
div
className=
"flex flex-col gap-4"
>
{
t
(
"setting.access-token-section.create-dialog.expiration"
)
}
<
span
className=
"text-destructive"
>
*
</
span
>
<
div
className=
"grid gap-2"
>
</
Label
>
<
Label
htmlFor=
"description"
>
<
RadioGroup
value=
{
state
.
expiration
.
toString
()
}
onValueChange=
{
handleRoleInputChange
}
className=
"flex flex-row gap-4"
>
{
t
(
"setting.access-token-section.create-dialog.description"
)
}
<
span
className=
"text-destructive"
>
*
</
span
>
{
expirationOptions
.
map
((
option
)
=>
(
</
Label
>
<
div
key=
{
option
.
value
}
className=
"flex items-center space-x-2"
>
<
Input
<
RadioGroupItem
value=
{
option
.
value
.
toString
()
}
id=
{
`expiration-${option.value}`
}
/>
id=
"description"
<
Label
htmlFor=
{
`expiration-${option.value}`
}
>
{
option
.
label
}
</
Label
>
type=
"text"
</
div
>
placeholder=
{
t
(
"setting.access-token-section.create-dialog.some-description"
)
}
))
}
value=
{
state
.
description
}
</
RadioGroup
>
onChange=
{
handleDescriptionInputChange
}
/>
</
div
>
<
div
className=
"grid gap-2"
>
<
Label
>
{
t
(
"setting.access-token-section.create-dialog.expiration"
)
}
<
span
className=
"text-destructive"
>
*
</
span
>
</
Label
>
<
RadioGroup
value=
{
state
.
expiration
.
toString
()
}
onValueChange=
{
handleRoleInputChange
}
className=
"flex flex-row gap-4"
>
{
expirationOptions
.
map
((
option
)
=>
(
<
div
key=
{
option
.
value
}
className=
"flex items-center space-x-2"
>
<
RadioGroupItem
value=
{
option
.
value
.
toString
()
}
id=
{
`expiration-${option.value}`
}
/>
<
Label
htmlFor=
{
`expiration-${option.value}`
}
>
{
option
.
label
}
</
Label
>
</
div
>
))
}
</
RadioGroup
>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
<
DialogFooter
>
<
DialogFooter
>
<
Button
variant=
"ghost"
disabled=
{
requestState
.
isLoading
}
onClick=
{
()
=>
onOpenChange
(
false
)
}
>
{
createdToken
?
(
{
t
(
"common.cancel"
)
}
<>
</
Button
>
<
Button
variant=
"ghost"
onClick=
{
handleCopyToken
}
>
<
Button
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.copy"
)
}
{
t
(
"common.create"
)
}
</
Button
>
</
Button
>
<
Button
onClick=
{
()
=>
onOpenChange
(
false
)
}
>
{
t
(
"common.close"
)
}
</
Button
>
</>
)
:
(
<>
<
Button
variant=
"ghost"
disabled=
{
requestState
.
isLoading
}
onClick=
{
()
=>
onOpenChange
(
false
)
}
>
{
t
(
"common.cancel"
)
}
</
Button
>
<
Button
disabled=
{
requestState
.
isLoading
}
onClick=
{
handleSaveBtnClick
}
>
{
t
(
"common.create"
)
}
</
Button
>
</>
)
}
</
DialogFooter
>
</
DialogFooter
>
</
DialogContent
>
</
DialogContent
>
</
Dialog
>
</
Dialog
>
...
...
web/src/components/Settings/AccessTokenSection.tsx
View file @
0dbc35a2
...
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
...
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import
{
userServiceClient
}
from
"@/connect"
;
import
{
userServiceClient
}
from
"@/connect"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
useDialog
}
from
"@/hooks/useDialog"
;
import
{
useDialog
}
from
"@/hooks/useDialog"
;
import
{
handleError
}
from
"@/lib/error"
;
import
{
CreatePersonalAccessTokenResponse
,
PersonalAccessToken
}
from
"@/types/proto/api/v1/user_service_pb"
;
import
{
CreatePersonalAccessTokenResponse
,
PersonalAccessToken
}
from
"@/types/proto/api/v1/user_service_pb"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
CreateAccessTokenDialog
from
"../CreateAccessTokenDialog"
;
import
CreateAccessTokenDialog
from
"../CreateAccessTokenDialog"
;
...
@@ -30,10 +31,23 @@ const AccessTokenSection = () => {
...
@@ -30,10 +31,23 @@ const AccessTokenSection = () => {
const
[
deleteTarget
,
setDeleteTarget
]
=
useState
<
PersonalAccessToken
|
undefined
>
(
undefined
);
const
[
deleteTarget
,
setDeleteTarget
]
=
useState
<
PersonalAccessToken
|
undefined
>
(
undefined
);
useEffect
(()
=>
{
useEffect
(()
=>
{
listAccessTokens
(
currentUser
?.
name
??
""
).
then
((
tokens
)
=>
{
if
(
!
currentUser
?.
name
)
return
;
setPersonalAccessTokens
(
tokens
);
let
canceled
=
false
;
});
listAccessTokens
(
currentUser
.
name
)
},
[]);
.
then
((
tokens
)
=>
{
if
(
!
canceled
)
{
setPersonalAccessTokens
(
tokens
);
}
})
.
catch
((
error
:
unknown
)
=>
{
if
(
!
canceled
)
{
handleError
(
error
,
toast
.
error
,
{
context
:
"List access tokens"
});
}
});
return
()
=>
{
canceled
=
true
;
};
},
[
currentUser
?.
name
]);
const
handleCreateAccessTokenDialogConfirm
=
async
(
response
:
CreatePersonalAccessTokenResponse
)
=>
{
const
handleCreateAccessTokenDialogConfirm
=
async
(
response
:
CreatePersonalAccessTokenResponse
)
=>
{
const
tokens
=
await
listAccessTokens
(
currentUser
?.
name
??
""
);
const
tokens
=
await
listAccessTokens
(
currentUser
?.
name
??
""
);
...
...
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