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
1ad5d9bf
Commit
1ad5d9bf
authored
Jul 01, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update confirm dialog
parent
4a3afffe
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
69 additions
and
213 deletions
+69
-213
CommonDialog.tsx
web/src/components/Dialog/CommonDialog.tsx
+0
-91
TagsSection.tsx
web/src/components/HomeSidebar/TagsSection.tsx
+9
-15
MemoActionMenu.tsx
web/src/components/MemoActionMenu.tsx
+8
-14
AccessTokenSection.tsx
web/src/components/Settings/AccessTokenSection.tsx
+7
-11
MemberSection.tsx
web/src/components/Settings/MemberSection.tsx
+18
-29
SSOSection.tsx
web/src/components/Settings/SSOSection.tsx
+10
-18
WebhookSection.tsx
web/src/components/Settings/WebhookSection.tsx
+5
-11
Archived.tsx
web/src/pages/Archived.tsx
+4
-10
Resources.tsx
web/src/pages/Resources.tsx
+8
-14
No files found.
web/src/components/Dialog/CommonDialog.tsx
deleted
100644 → 0
View file @
4a3afffe
import
{
Button
,
IconButton
}
from
"@mui/joy"
;
import
{
DefaultColorPalette
}
from
"@mui/joy/styles/types"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
Icon
from
"../Icon"
;
import
{
generateDialog
}
from
"./BaseDialog"
;
import
"@/less/common-dialog.less"
;
interface
Props
extends
DialogProps
{
title
:
string
;
content
:
string
;
style
?:
DefaultColorPalette
;
closeBtnText
?:
string
;
confirmBtnText
?:
string
;
onClose
?:
()
=>
void
;
onConfirm
?:
()
=>
void
;
}
const
defaultProps
=
{
title
:
""
,
content
:
""
,
style
:
"neutral"
,
closeBtnText
:
"common.close"
,
confirmBtnText
:
"common.confirm"
,
onClose
:
()
=>
null
,
onConfirm
:
()
=>
null
,
}
as
const
;
const
CommonDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
t
=
useTranslate
();
const
{
title
,
content
,
destroy
,
closeBtnText
,
confirmBtnText
,
onClose
,
onConfirm
,
style
}
=
{
...
defaultProps
,
closeBtnText
:
t
(
defaultProps
.
closeBtnText
),
confirmBtnText
:
t
(
defaultProps
.
confirmBtnText
),
...
props
,
};
const
handleCloseBtnClick
=
()
=>
{
onClose
();
destroy
();
};
const
handleConfirmBtnClick
=
async
()
=>
{
onConfirm
();
destroy
();
};
return
(
<>
<
div
className=
"dialog-header-container"
>
<
p
className=
"title-text"
>
{
title
}
</
p
>
<
IconButton
size=
"sm"
onClick=
{
handleCloseBtnClick
}
>
<
Icon
.
X
className=
"w-5 h-auto"
/>
</
IconButton
>
</
div
>
<
div
className=
"dialog-content-container"
>
<
p
className=
"content-text"
>
{
content
}
</
p
>
<
div
className=
"mt-4 w-full flex flex-row justify-end items-center gap-2"
>
<
Button
color=
"neutral"
variant=
"plain"
onClick=
{
handleCloseBtnClick
}
>
{
closeBtnText
}
</
Button
>
<
Button
color=
{
style
}
onClick=
{
handleConfirmBtnClick
}
>
{
confirmBtnText
}
</
Button
>
</
div
>
</
div
>
</>
);
};
interface
CommonDialogProps
{
title
:
string
;
content
:
string
;
className
?:
string
;
style
?:
DefaultColorPalette
;
dialogName
:
string
;
closeBtnText
?:
string
;
confirmBtnText
?:
string
;
onClose
?:
()
=>
void
;
onConfirm
?:
()
=>
void
;
}
export
const
showCommonDialog
=
(
props
:
CommonDialogProps
)
=>
{
generateDialog
(
{
className
:
`common-dialog
${
props
?.
className
??
""
}
`,
dialogName: `
common
-
dialog
$
{
props
?.
className
??
""
}
`,
},
CommonDialog,
props,
);
};
web/src/components/HomeSidebar/TagsSection.tsx
View file @
1ad5d9bf
...
@@ -8,7 +8,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
...
@@ -8,7 +8,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import
{
useFilterStore
}
from
"@/store/module"
;
import
{
useFilterStore
}
from
"@/store/module"
;
import
{
useMemoList
,
useTagStore
}
from
"@/store/v1"
;
import
{
useMemoList
,
useTagStore
}
from
"@/store/v1"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
import
showRenameTagDialog
from
"../RenameTagDialog"
;
import
showRenameTagDialog
from
"../RenameTagDialog"
;
...
@@ -42,20 +41,15 @@ const TagsSection = (props: Props) => {
...
@@ -42,20 +41,15 @@ const TagsSection = (props: Props) => {
};
};
const
handleDeleteTag
=
async
(
tag
:
string
)
=>
{
const
handleDeleteTag
=
async
(
tag
:
string
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
t
(
"tag.delete-confirm"
));
title
:
t
(
"tag.delete-tag"
),
if
(
confirmed
)
{
content
:
t
(
"tag.delete-confirm"
),
await
memoServiceClient
.
deleteMemoTag
({
style
:
"danger"
,
parent
:
"memos/-"
,
dialogName
:
"delete-tag-dialog"
,
tag
:
tag
,
onConfirm
:
async
()
=>
{
});
await
memoServiceClient
.
deleteMemoTag
({
await
tagStore
.
fetchTags
({
location
,
user
},
{
skipCache
:
true
});
parent
:
"memos/-"
,
toast
.
success
(
t
(
"message.deleted-successfully"
));
tag
:
tag
,
}
});
await
tagStore
.
fetchTags
({
location
,
user
},
{
skipCache
:
true
});
toast
.
success
(
t
(
"message.deleted-successfully"
));
},
});
};
};
return
(
return
(
...
...
web/src/components/MemoActionMenu.tsx
View file @
1ad5d9bf
...
@@ -9,7 +9,6 @@ import { useMemoStore } from "@/store/v1";
...
@@ -9,7 +9,6 @@ import { useMemoStore } from "@/store/v1";
import
{
RowStatus
}
from
"@/types/proto/api/v1/common"
;
import
{
RowStatus
}
from
"@/types/proto/api/v1/common"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
showCommonDialog
}
from
"./Dialog/CommonDialog"
;
import
showMemoEditorDialog
from
"./MemoEditor/MemoEditorDialog"
;
import
showMemoEditorDialog
from
"./MemoEditor/MemoEditorDialog"
;
interface
Props
{
interface
Props
{
...
@@ -95,19 +94,14 @@ const MemoActionMenu = (props: Props) => {
...
@@ -95,19 +94,14 @@ const MemoActionMenu = (props: Props) => {
};
};
const
handleDeleteMemoClick
=
async
()
=>
{
const
handleDeleteMemoClick
=
async
()
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
t
(
"memo.delete-confirm"
));
title
:
t
(
"memo.delete-memo"
),
if
(
confirmed
)
{
content
:
t
(
"memo.delete-confirm"
),
await
memoStore
.
deleteMemo
(
memo
.
name
);
style
:
"danger"
,
toast
.
success
(
t
(
"message.deleted-successfully"
));
dialogName
:
"delete-memo-dialog"
,
if
(
isInMemoDetailPage
)
{
onConfirm
:
async
()
=>
{
navigateTo
(
"/"
);
await
memoStore
.
deleteMemo
(
memo
.
name
);
}
toast
.
success
(
t
(
"message.deleted-successfully"
));
}
if
(
isInMemoDetailPage
)
{
navigateTo
(
"/"
);
}
},
});
};
};
return
(
return
(
...
...
web/src/components/Settings/AccessTokenSection.tsx
View file @
1ad5d9bf
...
@@ -7,7 +7,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
...
@@ -7,7 +7,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import
{
UserAccessToken
}
from
"@/types/proto/api/v1/user_service"
;
import
{
UserAccessToken
}
from
"@/types/proto/api/v1/user_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showCreateAccessTokenDialog
from
"../CreateAccessTokenDialog"
;
import
showCreateAccessTokenDialog
from
"../CreateAccessTokenDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
import
LearnMore
from
"../LearnMore"
;
import
LearnMore
from
"../LearnMore"
;
...
@@ -38,16 +37,13 @@ const AccessTokenSection = () => {
...
@@ -38,16 +37,13 @@ const AccessTokenSection = () => {
};
};
const
handleDeleteAccessToken
=
async
(
accessToken
:
string
)
=>
{
const
handleDeleteAccessToken
=
async
(
accessToken
:
string
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
title
:
"Delete Access Token"
,
`Are you sure to delete access token \`
${
getFormatedAccessToken
(
accessToken
)}
\`? You cannot undo this action.`
,
content
:
`Are you sure to delete access token \`
${
getFormatedAccessToken
(
accessToken
)}
\`? You cannot undo this action.`
,
);
style
:
"danger"
,
if
(
confirmed
)
{
dialogName
:
"delete-access-token-dialog"
,
await
userServiceClient
.
deleteUserAccessToken
({
name
:
currentUser
.
name
,
accessToken
:
accessToken
});
onConfirm
:
async
()
=>
{
setUserAccessTokens
(
userAccessTokens
.
filter
((
token
)
=>
token
.
accessToken
!==
accessToken
));
await
userServiceClient
.
deleteUserAccessToken
({
name
:
currentUser
.
name
,
accessToken
:
accessToken
});
}
setUserAccessTokens
(
userAccessTokens
.
filter
((
token
)
=>
token
.
accessToken
!==
accessToken
));
},
});
};
};
const
getFormatedAccessToken
=
(
accessToken
:
string
)
=>
{
const
getFormatedAccessToken
=
(
accessToken
:
string
)
=>
{
...
...
web/src/components/Settings/MemberSection.tsx
View file @
1ad5d9bf
...
@@ -9,7 +9,6 @@ import { RowStatus } from "@/types/proto/api/v1/common";
...
@@ -9,7 +9,6 @@ import { RowStatus } from "@/types/proto/api/v1/common";
import
{
User
,
User_Role
}
from
"@/types/proto/api/v1/user_service"
;
import
{
User
,
User_Role
}
from
"@/types/proto/api/v1/user_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showChangeMemberPasswordDialog
from
"../ChangeMemberPasswordDialog"
;
import
showChangeMemberPasswordDialog
from
"../ChangeMemberPasswordDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
interface
State
{
interface
State
{
...
@@ -101,23 +100,18 @@ const MemberSection = () => {
...
@@ -101,23 +100,18 @@ const MemberSection = () => {
showChangeMemberPasswordDialog
(
user
);
showChangeMemberPasswordDialog
(
user
);
};
};
const
handleArchiveUserClick
=
(
user
:
User
)
=>
{
const
handleArchiveUserClick
=
async
(
user
:
User
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
t
(
"setting.member-section.archive-warning"
,
{
username
:
user
.
nickname
}));
title
:
t
(
"setting.member-section.archive-member"
),
if
(
confirmed
)
{
content
:
t
(
"setting.member-section.archive-warning"
,
{
username
:
user
.
nickname
}),
await
userServiceClient
.
updateUser
({
style
:
"danger"
,
user
:
{
dialogName
:
"archive-user-dialog"
,
name
:
user
.
name
,
onConfirm
:
async
()
=>
{
rowStatus
:
RowStatus
.
ARCHIVED
,
await
userServiceClient
.
updateUser
({
},
user
:
{
updateMask
:
[
"row_status"
],
name
:
user
.
name
,
});
rowStatus
:
RowStatus
.
ARCHIVED
,
fetchUsers
();
},
}
updateMask
:
[
"row_status"
],
});
fetchUsers
();
},
});
};
};
const
handleRestoreUserClick
=
async
(
user
:
User
)
=>
{
const
handleRestoreUserClick
=
async
(
user
:
User
)
=>
{
...
@@ -131,17 +125,12 @@ const MemberSection = () => {
...
@@ -131,17 +125,12 @@ const MemberSection = () => {
fetchUsers
();
fetchUsers
();
};
};
const
handleDeleteUserClick
=
(
user
:
User
)
=>
{
const
handleDeleteUserClick
=
async
(
user
:
User
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
t
(
"setting.member-section.delete-warning"
,
{
username
:
user
.
nickname
}));
title
:
t
(
"setting.member-section.delete-member"
),
if
(
confirmed
)
{
content
:
t
(
"setting.member-section.delete-warning"
,
{
username
:
user
.
nickname
}),
await
userStore
.
deleteUser
(
user
.
name
);
style
:
"danger"
,
fetchUsers
();
dialogName
:
"delete-user-dialog"
,
}
onConfirm
:
async
()
=>
{
await
userStore
.
deleteUser
(
user
.
name
);
fetchUsers
();
},
});
};
};
return
(
return
(
...
...
web/src/components/Settings/SSOSection.tsx
View file @
1ad5d9bf
...
@@ -6,7 +6,6 @@ import { identityProviderServiceClient } from "@/grpcweb";
...
@@ -6,7 +6,6 @@ import { identityProviderServiceClient } from "@/grpcweb";
import
{
IdentityProvider
}
from
"@/types/proto/api/v1/idp_service"
;
import
{
IdentityProvider
}
from
"@/types/proto/api/v1/idp_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showCreateIdentityProviderDialog
from
"../CreateIdentityProviderDialog"
;
import
showCreateIdentityProviderDialog
from
"../CreateIdentityProviderDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
import
LearnMore
from
"../LearnMore"
;
import
LearnMore
from
"../LearnMore"
;
...
@@ -24,23 +23,16 @@ const SSOSection = () => {
...
@@ -24,23 +23,16 @@ const SSOSection = () => {
};
};
const
handleDeleteIdentityProvider
=
async
(
identityProvider
:
IdentityProvider
)
=>
{
const
handleDeleteIdentityProvider
=
async
(
identityProvider
:
IdentityProvider
)
=>
{
const
content
=
t
(
"setting.sso-section.confirm-delete"
,
{
name
:
identityProvider
.
title
});
const
confirmed
=
window
.
confirm
(
t
(
"setting.sso-section.confirm-delete"
,
{
name
:
identityProvider
.
title
}));
if
(
confirmed
)
{
showCommonDialog
({
try
{
title
:
t
(
"setting.sso-section.delete-sso"
),
await
identityProviderServiceClient
.
deleteIdentityProvider
({
name
:
identityProvider
.
name
});
content
:
content
,
}
catch
(
error
:
any
)
{
style
:
"danger"
,
console
.
error
(
error
);
dialogName
:
"delete-identity-provider-dialog"
,
toast
.
error
(
error
.
details
);
onConfirm
:
async
()
=>
{
}
try
{
await
fetchIdentityProviderList
();
await
identityProviderServiceClient
.
deleteIdentityProvider
({
name
:
identityProvider
.
name
});
}
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
details
);
}
await
fetchIdentityProviderList
();
},
});
};
};
return
(
return
(
...
...
web/src/components/Settings/WebhookSection.tsx
View file @
1ad5d9bf
...
@@ -6,7 +6,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
...
@@ -6,7 +6,6 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import
{
Webhook
}
from
"@/types/proto/api/v1/webhook_service"
;
import
{
Webhook
}
from
"@/types/proto/api/v1/webhook_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showCreateWebhookDialog
from
"../CreateWebhookDialog"
;
import
showCreateWebhookDialog
from
"../CreateWebhookDialog"
;
import
{
showCommonDialog
}
from
"../Dialog/CommonDialog"
;
import
Icon
from
"../Icon"
;
import
Icon
from
"../Icon"
;
const
listWebhooks
=
async
(
userId
:
number
)
=>
{
const
listWebhooks
=
async
(
userId
:
number
)
=>
{
...
@@ -33,16 +32,11 @@ const WebhookSection = () => {
...
@@ -33,16 +32,11 @@ const WebhookSection = () => {
};
};
const
handleDeleteWebhook
=
async
(
webhook
:
Webhook
)
=>
{
const
handleDeleteWebhook
=
async
(
webhook
:
Webhook
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
`Are you sure to delete webhook \`
${
webhook
.
name
}
\`? You cannot undo this action.`
);
title
:
"Delete Webhook"
,
if
(
confirmed
)
{
content
:
`Are you sure to delete webhook \`
${
webhook
.
name
}
\`? You cannot undo this action.`
,
await
webhookServiceClient
.
deleteWebhook
({
id
:
webhook
.
id
});
style
:
"danger"
,
setWebhooks
(
webhooks
.
filter
((
item
)
=>
item
.
id
!==
webhook
.
id
));
dialogName
:
"delete-webhook-dialog"
,
}
onConfirm
:
async
()
=>
{
await
webhookServiceClient
.
deleteWebhook
({
id
:
webhook
.
id
});
setWebhooks
(
webhooks
.
filter
((
item
)
=>
item
.
id
!==
webhook
.
id
));
},
});
};
};
return
(
return
(
...
...
web/src/pages/Archived.tsx
View file @
1ad5d9bf
...
@@ -2,7 +2,6 @@ import { Button, Tooltip } from "@mui/joy";
...
@@ -2,7 +2,6 @@ import { Button, Tooltip } from "@mui/joy";
import
{
ClientError
}
from
"nice-grpc-web"
;
import
{
ClientError
}
from
"nice-grpc-web"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
toast
from
"react-hot-toast"
;
import
{
showCommonDialog
}
from
"@/components/Dialog/CommonDialog"
;
import
Empty
from
"@/components/Empty"
;
import
Empty
from
"@/components/Empty"
;
import
Icon
from
"@/components/Icon"
;
import
Icon
from
"@/components/Icon"
;
import
MemoContent
from
"@/components/MemoContent"
;
import
MemoContent
from
"@/components/MemoContent"
;
...
@@ -57,15 +56,10 @@ const Archived = () => {
...
@@ -57,15 +56,10 @@ const Archived = () => {
};
};
const
handleDeleteMemoClick
=
async
(
memo
:
Memo
)
=>
{
const
handleDeleteMemoClick
=
async
(
memo
:
Memo
)
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
t
(
"memo.delete-confirm"
));
title
:
t
(
"memo.delete-memo"
),
if
(
confirmed
)
{
content
:
t
(
"memo.delete-confirm"
),
await
memoStore
.
deleteMemo
(
memo
.
name
);
style
:
"danger"
,
}
dialogName
:
"delete-memo-dialog"
,
onConfirm
:
async
()
=>
{
await
memoStore
.
deleteMemo
(
memo
.
name
);
},
});
};
};
const
handleRestoreMemoClick
=
async
(
memo
:
Memo
)
=>
{
const
handleRestoreMemoClick
=
async
(
memo
:
Memo
)
=>
{
...
...
web/src/pages/Resources.tsx
View file @
1ad5d9bf
import
{
Divider
,
IconButton
,
Input
,
Tooltip
}
from
"@mui/joy"
;
import
{
Divider
,
IconButton
,
Input
,
Tooltip
}
from
"@mui/joy"
;
import
{
includes
}
from
"lodash-es"
;
import
{
includes
}
from
"lodash-es"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
showCommonDialog
}
from
"@/components/Dialog/CommonDialog"
;
import
Empty
from
"@/components/Empty"
;
import
Empty
from
"@/components/Empty"
;
import
Icon
from
"@/components/Icon"
;
import
Icon
from
"@/components/Icon"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
...
@@ -58,19 +57,14 @@ const Resources = () => {
...
@@ -58,19 +57,14 @@ const Resources = () => {
});
});
},
[]);
},
[]);
const
handleDeleteUnusedResources
=
()
=>
{
const
handleDeleteUnusedResources
=
async
()
=>
{
showCommonDialog
({
const
confirmed
=
window
.
confirm
(
"Are you sure to delete all unused resources? This action cannot be undone."
);
title
:
"Delete all unused resources"
,
if
(
confirmed
)
{
content
:
"Are you sure to delete all unused resources? This action cannot be undone."
,
for
(
const
resource
of
unusedResources
)
{
style
:
"warning"
,
await
resourceServiceClient
.
deleteResource
({
name
:
resource
.
name
});
dialogName
:
"delete-unused-resources-dialog"
,
}
onConfirm
:
async
()
=>
{
setResources
(
resources
.
filter
((
resource
)
=>
resource
.
memo
));
for
(
const
resource
of
unusedResources
)
{
}
await
resourceServiceClient
.
deleteResource
({
name
:
resource
.
name
});
}
setResources
(
resources
.
filter
((
resource
)
=>
resource
.
memo
));
},
});
};
};
return
(
return
(
...
...
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