web-admin: fix password reset not copying to clipboard

The resetPassword struct lacked a json tag, causing Go to serialize the
field as "Password" (capital P) while the frontend read "password"
(lowercase), always getting undefined. Also display the generated
password in a copyable input field as a fallback when clipboard access
fails.
This commit is contained in:
nemunaire 2026-05-20 17:12:26 +08:00
commit d7dc673f95
2 changed files with 51 additions and 22 deletions

View file

@ -224,7 +224,7 @@ func (ac *AuthUserController) RecoverUserAcct(c *gin.Context) {
}
type resetPassword struct {
Password string
Password string `json:"password"`
}
// ResetUserPasswd resets a user's password.

View file

@ -23,52 +23,72 @@
<script lang="ts">
import {
Alert,
Button,
Card,
CardBody,
CardHeader,
Icon,
Input,
InputGroup,
Spinner,
} from "@sveltestrap/sveltestrap";
import { postAuthByUidResetPassword } from '$lib/api-admin';
import { toasts } from '$lib/stores/toasts';
import { postAuthByUidResetPassword } from "$lib/api-admin";
import { toasts } from "$lib/stores/toasts";
interface Props {
uid: string;
}
let {
uid,
}: Props = $props();
let { uid }: Props = $props();
let actionLoading = $state(false);
let generatedPassword = $state("");
async function handleResetPassword() {
if (!confirm('Are you sure you want to reset this user\'s password? A random password will be generated.')) return;
if (
!confirm(
"Are you sure you want to reset this user's password? A random password will be generated.",
)
)
return;
actionLoading = true;
generatedPassword = "";
try {
const response = await postAuthByUidResetPassword({
path: { uid },
body: { password: '' }
});
const password = response.data?.password || '';
await navigator.clipboard.writeText(password);
toasts.addToast({
message: 'Password reset successfully and copied to clipboard',
type: 'success',
timeout: 5000,
body: { password: "" },
});
generatedPassword = response.data?.password || "";
await copyPassword();
} catch (error) {
toasts.addErrorToast({
message: 'Failed to reset password: ' + error,
message: "Failed to reset password: " + error,
timeout: 10000,
});
} finally {
actionLoading = false;
}
}
async function copyPassword() {
try {
await navigator.clipboard.writeText(generatedPassword);
toasts.addToast({
message: "Password reset successfully and copied to clipboard.",
type: "success",
timeout: 5000,
});
} catch {
toasts.addToast({
message: "Password reset successfully. Copy it manually below.",
type: "success",
timeout: 5000,
});
}
}
</script>
<Card class="mb-4">
@ -79,12 +99,21 @@
</h5>
</CardHeader>
<CardBody class="d-flex flex-column gap-2">
<Button
color="danger"
outline
onclick={handleResetPassword}
disabled={actionLoading}
>
{#if generatedPassword}
<Alert color="success">
<p class="mb-2">
<Icon name="check-circle" class="me-1" />
Password reset. Share this with the user — it won't be shown again.
</p>
<InputGroup>
<Input type="text" value={generatedPassword} readonly />
<Button color="secondary" outline onclick={copyPassword}>
<Icon name="clipboard" />
</Button>
</InputGroup>
</Alert>
{/if}
<Button color="danger" outline onclick={handleResetPassword} disabled={actionLoading}>
{#if actionLoading}
<Spinner size="sm" class="me-2" />
{:else}