@meith/web 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/app/(board)/member/[id]/reputation/page.tsx +2 -2
  2. package/app/(board)/messages/[id]/page.tsx +28 -20
  3. package/app/(board)/messages/page.tsx +2 -2
  4. package/app/(board)/notifications/page.tsx +3 -1
  5. package/app/(board)/subscriptions/page.tsx +6 -2
  6. package/app/(board)/usercp/contacts/page.tsx +3 -7
  7. package/app/admin/api-tokens/page.tsx +3 -2
  8. package/app/admin/forums/[id]/page.tsx +25 -22
  9. package/app/admin/groups/memberships/page.tsx +6 -6
  10. package/app/admin/groups/promotions/page.tsx +14 -16
  11. package/app/admin/log/page.tsx +5 -3
  12. package/app/admin/plugins/page.tsx +1 -1
  13. package/app/admin/security/page.tsx +4 -3
  14. package/app/admin/system/page.tsx +13 -10
  15. package/app/admin/themes/page.tsx +1 -1
  16. package/bin/forum-web.mjs +3 -3
  17. package/bin/forum-web.test.ts +2 -2
  18. package/package.json +48 -48
  19. package/src/components/account/active-sessions.tsx +9 -6
  20. package/src/components/account/credential-proof-form.tsx +6 -7
  21. package/src/components/account/logout-form.tsx +5 -3
  22. package/src/components/account/notification-forms.tsx +9 -9
  23. package/src/components/account/relation-forms.tsx +5 -5
  24. package/src/components/account/reputation-forms.tsx +10 -10
  25. package/src/components/account/sign-in-methods.tsx +12 -6
  26. package/src/components/account/subscription-forms.tsx +7 -7
  27. package/src/components/account/two-factor-forms.tsx +12 -12
  28. package/src/components/account/usercp-forms.tsx +13 -13
  29. package/src/components/admin/admin-forms.tsx +7 -7
  30. package/src/components/admin/badge-forms.tsx +3 -3
  31. package/src/components/admin/ban-filter-forms.tsx +4 -4
  32. package/src/components/admin/branding-forms.tsx +3 -3
  33. package/src/components/admin/content-forms.tsx +15 -15
  34. package/src/components/admin/forum-forms.tsx +7 -7
  35. package/src/components/admin/group-forms.tsx +5 -5
  36. package/src/components/admin/mail-test-card.tsx +3 -3
  37. package/src/components/admin/marketplace-forms.tsx +4 -4
  38. package/src/components/admin/navigation-forms.tsx +3 -3
  39. package/src/components/admin/plugin-forms.tsx +7 -7
  40. package/src/components/admin/settings-form.tsx +5 -2
  41. package/src/components/admin/theme-forms.tsx +7 -10
  42. package/src/components/auth/form-controls.tsx +23 -1
  43. package/src/components/auth/second-factor-form.tsx +4 -4
  44. package/src/components/content/composer-intents.tsx +7 -6
  45. package/src/components/content/edit-post-form.tsx +7 -7
  46. package/src/components/content/poll-edit-form.tsx +3 -1
  47. package/src/components/content/poll.tsx +4 -2
  48. package/src/components/content/thanks-button.tsx +3 -3
  49. package/src/components/content/thread-rating.tsx +3 -3
  50. package/src/components/messages/message-forms.tsx +15 -15
  51. package/src/components/moderation/inline-moderation-form.tsx +19 -20
  52. package/src/components/moderation/queue-form.tsx +5 -7
  53. package/src/components/moderation/report-forms.tsx +8 -10
  54. package/src/components/moderation/signature-lock-form.tsx +5 -5
  55. package/src/components/moderation/thread-surgery-form.tsx +8 -5
  56. package/src/components/moderation/thread-tools-form.tsx +11 -22
  57. package/src/components/moderation/warning-forms.tsx +6 -6
  58. package/src/components/shell/onboarding-banner.tsx +6 -2
  59. package/src/components/shell/theme-switcher.tsx +6 -6
  60. package/src/components/shell/view-tabs.tsx +12 -7
  61. package/src/server/theme.ts +1 -1
  62. package/src/server/upgrade-notice.ts +1 -1
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { useActionState } from 'react'
4
4
 
5
- import { Button } from '@meith/ui/button'
5
+ import { buttonVariants } from '@meith/ui'
6
6
 
7
7
  import { EMPTY_STATE } from '@/server/auth-form-state'
8
8
  import { revokeOtherSessionsAction, revokeSessionAction } from '@/server/session-actions'
9
9
 
10
- import { FormError } from '../auth/form-controls'
10
+ import { FormError, PendingButton } from '../auth/form-controls'
11
11
  import { type Copy, fromCopy } from '../shell/copy'
12
12
 
13
13
  const CARD = 'flex flex-col gap-4 rounded-lg border border-border bg-card p-5'
@@ -66,9 +66,12 @@ export function ActiveSessions({
66
66
  {session.current ? null : (
67
67
  <form action={revoke}>
68
68
  <input type="hidden" name="sessionId" value={session.id} />
69
- <Button type="submit" variant="destructive" size="sm">
69
+ <PendingButton
70
+ showWorking
71
+ className={buttonVariants({ variant: 'destructive', size: 'sm' })}
72
+ >
70
73
  {fromCopy(copy, 'accountForm.sessions.signOut')}
71
- </Button>
74
+ </PendingButton>
72
75
  </form>
73
76
  )}
74
77
  </li>
@@ -77,9 +80,9 @@ export function ActiveSessions({
77
80
 
78
81
  {others > 0 ? (
79
82
  <form action={revokeAll}>
80
- <Button type="submit" variant="outline" size="sm">
83
+ <PendingButton showWorking className={buttonVariants({ variant: 'outline', size: 'sm' })}>
81
84
  {fromCopy(copy, 'accountForm.sessions.signOutElsewhere')}
82
- </Button>
85
+ </PendingButton>
83
86
  </form>
84
87
  ) : null}
85
88
  </section>
@@ -2,13 +2,12 @@
2
2
 
3
3
  import { useActionState } from 'react'
4
4
 
5
- import { Input } from '@meith/ui'
6
- import { Button } from '@meith/ui/button'
5
+ import { buttonVariants, Input } from '@meith/ui'
7
6
 
8
7
  import { EMPTY_STATE } from '@/server/auth-form-state'
9
8
  import { proveCredentialAction } from '@/server/credential-proof-actions'
10
9
 
11
- import { FormError } from '../auth/form-controls'
10
+ import { FormError, PendingButton } from '../auth/form-controls'
12
11
  import { OtpField } from '../auth/otp-field'
13
12
 
14
13
  export interface CredentialProofCopy {
@@ -50,9 +49,9 @@ export function CredentialProofForm({
50
49
  <Input name="password" type="password" autoComplete="current-password" required />
51
50
  </label>
52
51
  <div>
53
- <Button type="submit" variant="primary">
52
+ <PendingButton showWorking className={buttonVariants({ variant: 'primary' })}>
54
53
  {copy.passwordSubmit}
55
- </Button>
54
+ </PendingButton>
56
55
  </div>
57
56
  </form>
58
57
  ) : null}
@@ -75,9 +74,9 @@ export function CredentialProofForm({
75
74
  />
76
75
  <p className="text-sm text-muted-foreground">{copy.codeConsumed}</p>
77
76
  <div>
78
- <Button type="submit" variant="primary">
77
+ <PendingButton showWorking className={buttonVariants({ variant: 'primary' })}>
79
78
  {copy.codeSubmit}
80
- </Button>
79
+ </PendingButton>
81
80
  </div>
82
81
  </form>
83
82
  ) : null}
@@ -1,14 +1,16 @@
1
1
  import { logoutAction } from '@/server/auth-actions'
2
2
 
3
+ import { PendingButton } from '../auth/form-controls'
4
+
3
5
  export function LogoutForm({ label }: { label: string }) {
4
6
  return (
5
7
  <form action={logoutAction}>
6
- <button
7
- type="submit"
8
+ <PendingButton
9
+ showWorking
8
10
  className="font-medium text-foreground hover:underline underline-offset-2"
9
11
  >
10
12
  {label}
11
- </button>
13
+ </PendingButton>
12
14
  </form>
13
15
  )
14
16
  }
@@ -10,7 +10,7 @@ import {
10
10
  saveNotificationPreferencesAction,
11
11
  } from '@/server/notification-actions'
12
12
 
13
- import { FormError } from '../auth/form-controls'
13
+ import { FormError, PendingButton } from '../auth/form-controls'
14
14
  import { type Copy, fromCopy } from '../shell/copy'
15
15
 
16
16
  const BUTTON =
@@ -32,9 +32,9 @@ export function MarkNotificationReadForm({
32
32
  <form action={action} className="contents">
33
33
  <input type="hidden" name="notificationId" value={notificationId} />
34
34
  <FormError message={state.error} />
35
- <button type="submit" className={LINK_BUTTON}>
35
+ <PendingButton showWorking className={LINK_BUTTON}>
36
36
  {fromCopy(copy, 'accountForm.notifications.markRead')}
37
- </button>
37
+ </PendingButton>
38
38
  </form>
39
39
  )
40
40
  }
@@ -47,9 +47,9 @@ export function MarkAllNotificationsReadForm({ unread, copy }: { unread: number;
47
47
  return (
48
48
  <form action={action}>
49
49
  <FormError message={state.error} />
50
- <button type="submit" className={BUTTON}>
50
+ <PendingButton showWorking className={BUTTON}>
51
51
  {fromCopy(copy, 'accountForm.notifications.markAllRead')}
52
- </button>
52
+ </PendingButton>
53
53
  </form>
54
54
  )
55
55
  }
@@ -149,9 +149,9 @@ export function NotificationPreferencesForm({
149
149
  </p>
150
150
 
151
151
  <div>
152
- <button type="submit" className={BUTTON}>
152
+ <PendingButton showWorking className={BUTTON}>
153
153
  {fromCopy(copy, 'accountForm.notifications.submit')}
154
- </button>
154
+ </PendingButton>
155
155
  </div>
156
156
  </form>
157
157
  )
@@ -193,9 +193,9 @@ export function AnnouncementsOptInForm({ optedIn, copy }: { optedIn: boolean; co
193
193
  </p>
194
194
 
195
195
  <div>
196
- <button type="submit" className={BUTTON}>
196
+ <PendingButton showWorking className={BUTTON}>
197
197
  {fromCopy(copy, 'accountForm.announcements.submit')}
198
- </button>
198
+ </PendingButton>
199
199
  </div>
200
200
  </form>
201
201
  )
@@ -5,7 +5,7 @@ import { useActionState } from 'react'
5
5
  import { EMPTY_STATE } from '@/server/auth-form-state'
6
6
  import { removeRelationAction, setRelationAction } from '@/server/relation-actions'
7
7
 
8
- import { FormError } from '../auth/form-controls'
8
+ import { FormError, PendingButton } from '../auth/form-controls'
9
9
 
10
10
  const LINK_BUTTON =
11
11
  'text-sm font-medium text-foreground underline decoration-border underline-offset-2 hover:decoration-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
@@ -35,9 +35,9 @@ export function SetRelationForm({
35
35
  <input type="hidden" name="username" value={username} />
36
36
  <input type="hidden" name="kind" value={kind} />
37
37
  <input type="hidden" name="returnTo" value={returnTo} />
38
- <button type="submit" className={kind === 'buddy' ? LINK_BUTTON : QUIET_BUTTON}>
38
+ <PendingButton showWorking className={kind === 'buddy' ? LINK_BUTTON : QUIET_BUTTON}>
39
39
  {label}
40
- </button>
40
+ </PendingButton>
41
41
  </form>
42
42
  )
43
43
  }
@@ -61,9 +61,9 @@ export function RemoveRelationForm({
61
61
  <input type="hidden" name="userId" value={userId} />
62
62
  <input type="hidden" name="username" value={username} />
63
63
  <input type="hidden" name="returnTo" value={returnTo} />
64
- <button type="submit" className={QUIET_BUTTON}>
64
+ <PendingButton showWorking className={QUIET_BUTTON}>
65
65
  {label}
66
- </button>
66
+ </PendingButton>
67
67
  </form>
68
68
  )
69
69
  }
@@ -5,7 +5,7 @@ import { useActionState } from 'react'
5
5
  import { EMPTY_STATE } from '@/server/auth-form-state'
6
6
  import { rateMemberAction, withdrawRatingAction } from '@/server/reputation-actions'
7
7
 
8
- import { FormError } from '../auth/form-controls'
8
+ import { FormError, PendingButton } from '../auth/form-controls'
9
9
  import { type Copy, fromCopy } from '../shell/copy'
10
10
 
11
11
  const FIELD =
@@ -72,20 +72,20 @@ export function RateMemberForm({
72
72
  </label>
73
73
 
74
74
  <div className="flex flex-wrap items-center gap-2">
75
- <button type="submit" name="points" value="1" className={CHOICE}>
75
+ <PendingButton name="points" value="1" className={CHOICE}>
76
76
  {allowNegative
77
77
  ? fromCopy(copy, 'accountForm.rate.positive')
78
78
  : fromCopy(copy, 'accountForm.rate.thanks')}
79
- </button>
80
- <button type="submit" name="points" value="0" className={CHOICE}>
79
+ </PendingButton>
80
+ <PendingButton name="points" value="0" className={CHOICE}>
81
81
  {allowNegative
82
82
  ? fromCopy(copy, 'accountForm.rate.neutral')
83
83
  : fromCopy(copy, 'accountForm.rate.justComment')}
84
- </button>
84
+ </PendingButton>
85
85
  {allowNegative && (
86
- <button type="submit" name="points" value="-1" className={CHOICE}>
86
+ <PendingButton name="points" value="-1" className={CHOICE}>
87
87
  {fromCopy(copy, 'accountForm.rate.negative')}
88
- </button>
88
+ </PendingButton>
89
89
  )}
90
90
  </div>
91
91
 
@@ -113,12 +113,12 @@ export function WithdrawRatingForm({
113
113
  <input type="hidden" name="ratingId" value={ratingId} />
114
114
  <input type="hidden" name="userId" value={userId} />
115
115
  <input type="hidden" name="returnTo" value={returnTo} />
116
- <button
117
- type="submit"
116
+ <PendingButton
117
+ showWorking
118
118
  className="text-xs text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
119
119
  >
120
120
  {fromCopy(copy, 'accountForm.rate.withdraw')}
121
- </button>
121
+ </PendingButton>
122
122
  </form>
123
123
  )
124
124
  }
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { useActionState } from 'react'
4
4
 
5
- import { Button } from '@meith/ui/button'
5
+ import { buttonVariants } from '@meith/ui'
6
6
 
7
7
  import { EMPTY_STATE } from '@/server/auth-form-state'
8
8
  import { removePasskeyAction, unlinkIdentityAction } from '@/server/federation-actions'
9
9
 
10
- import { FormError } from '../auth/form-controls'
10
+ import { FormError, PendingButton } from '../auth/form-controls'
11
11
  import { type Copy, fromCopy } from '../shell/copy'
12
12
 
13
13
  const CARD = 'flex flex-col gap-4 rounded-lg border border-border bg-card p-5'
@@ -79,9 +79,12 @@ export function LinkedSignIns({
79
79
  {manageable ? (
80
80
  <form action={action}>
81
81
  <input type="hidden" name="identityId" value={identity.id} />
82
- <Button type="submit" variant="destructive" size="sm">
82
+ <PendingButton
83
+ showWorking
84
+ className={buttonVariants({ variant: 'destructive', size: 'sm' })}
85
+ >
83
86
  {fromCopy(copy, 'accountForm.linked.unlink')}
84
- </Button>
87
+ </PendingButton>
85
88
  </form>
86
89
  ) : null}
87
90
  </li>
@@ -150,9 +153,12 @@ export function PasskeyList({
150
153
  {manageable ? (
151
154
  <form action={action}>
152
155
  <input type="hidden" name="passkeyId" value={passkey.id} />
153
- <Button type="submit" variant="destructive" size="sm">
156
+ <PendingButton
157
+ showWorking
158
+ className={buttonVariants({ variant: 'destructive', size: 'sm' })}
159
+ >
154
160
  {fromCopy(copy, 'accountForm.passkeys.remove')}
155
- </Button>
161
+ </PendingButton>
156
162
  </form>
157
163
  ) : null}
158
164
  </li>
@@ -9,7 +9,7 @@ import {
9
9
  unsubscribeByTokenAction,
10
10
  } from '@/server/subscription-actions'
11
11
 
12
- import { FormError } from '../auth/form-controls'
12
+ import { FormError, PendingButton } from '../auth/form-controls'
13
13
  import { type Copy, fromCopy } from '../shell/copy'
14
14
 
15
15
  const BUTTON =
@@ -77,11 +77,11 @@ export function FollowForm({
77
77
  </select>
78
78
  </label>
79
79
 
80
- <button type="submit" className={QUIET_BUTTON}>
80
+ <PendingButton className={QUIET_BUTTON}>
81
81
  {mode === null
82
82
  ? fromCopy(copy, 'accountForm.follow.follow')
83
83
  : fromCopy(copy, 'accountForm.follow.save')}
84
- </button>
84
+ </PendingButton>
85
85
  </form>
86
86
 
87
87
  {mode !== null && (
@@ -89,9 +89,9 @@ export function FollowForm({
89
89
  <input type="hidden" name="target" value={target} />
90
90
  <input type="hidden" name="targetId" value={targetId} />
91
91
  <input type="hidden" name="back" value={back} />
92
- <button type="submit" className={GHOST_BUTTON}>
92
+ <PendingButton className={GHOST_BUTTON}>
93
93
  {fromCopy(copy, 'accountForm.follow.stop')}
94
- </button>
94
+ </PendingButton>
95
95
  </form>
96
96
  )}
97
97
  </div>
@@ -142,9 +142,9 @@ export function UnsubscribeConfirmForm({
142
142
  <input type="hidden" name="token" value={token} />
143
143
  <p className="text-sm">{description}</p>
144
144
  <div>
145
- <button type="submit" className={BUTTON}>
145
+ <PendingButton showWorking className={BUTTON}>
146
146
  {fromCopy(copy, 'accountForm.follow.unsubscribe')}
147
- </button>
147
+ </PendingButton>
148
148
  </div>
149
149
  </form>
150
150
  )
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useActionState } from 'react'
4
4
 
5
- import { Button } from '@meith/ui/button'
5
+ import { buttonVariants } from '@meith/ui'
6
6
 
7
7
  import { EMPTY_STATE, type FormState } from '@/server/auth-form-state'
8
8
  import {
@@ -14,7 +14,7 @@ import {
14
14
  } from '@/server/two-factor-actions'
15
15
  import { RECOVERY_CODES_FIELD } from '@/view/two-factor'
16
16
 
17
- import { Field, FormError } from '../auth/form-controls'
17
+ import { Field, FormError, PendingButton } from '../auth/form-controls'
18
18
  import { OtpField, otpRecoveryFromCopy } from '../auth/otp-field'
19
19
  import { type Copy, fromCopy } from '../shell/copy'
20
20
 
@@ -103,17 +103,17 @@ export function TwoFactorSetup({
103
103
  hint={fromCopy(copy, 'accountForm.twoFactor.codeHint')}
104
104
  />
105
105
  <div className="flex flex-wrap gap-2">
106
- <button type="submit" className={BUTTON}>
106
+ <PendingButton showWorking className={BUTTON}>
107
107
  {fromCopy(copy, 'accountForm.twoFactor.turnOn')}
108
- </button>
108
+ </PendingButton>
109
109
  </div>
110
110
  </form>
111
111
 
112
112
  <form action={abandon}>
113
113
  <FormError message={abandonState.error} />
114
- <Button type="submit" variant="ghost" size="sm">
114
+ <PendingButton showWorking className={buttonVariants({ variant: 'ghost', size: 'sm' })}>
115
115
  {fromCopy(copy, 'accountForm.twoFactor.cancel')}
116
- </Button>
116
+ </PendingButton>
117
117
  </form>
118
118
  </section>
119
119
  )
@@ -148,9 +148,9 @@ export function TwoFactorPanel({
148
148
  </p>
149
149
  <FormError message={beginState.error} />
150
150
  <form action={begin}>
151
- <button type="submit" className={BUTTON}>
151
+ <PendingButton showWorking className={BUTTON}>
152
152
  {fromCopy(copy, 'accountForm.twoFactor.begin')}
153
- </button>
153
+ </PendingButton>
154
154
  </form>
155
155
  </section>
156
156
  )
@@ -199,9 +199,9 @@ export function TwoFactorPanel({
199
199
  </p>
200
200
  {proof('replace')}
201
201
  <div>
202
- <button type="submit" className={BUTTON}>
202
+ <PendingButton showWorking className={BUTTON}>
203
203
  {fromCopy(copy, 'accountForm.twoFactor.replaceSubmit')}
204
- </button>
204
+ </PendingButton>
205
205
  </div>
206
206
  </form>
207
207
 
@@ -215,9 +215,9 @@ export function TwoFactorPanel({
215
215
  <p className="text-sm font-medium">{fromCopy(copy, 'accountForm.twoFactor.offTitle')}</p>
216
216
  {proof('disable')}
217
217
  <div>
218
- <Button type="submit" variant="destructive">
218
+ <PendingButton showWorking className={buttonVariants({ variant: 'destructive' })}>
219
219
  {fromCopy(copy, 'accountForm.twoFactor.offSubmit')}
220
- </Button>
220
+ </PendingButton>
221
221
  </div>
222
222
  </form>
223
223
  )}
@@ -12,7 +12,7 @@ import {
12
12
  saveSignatureAction,
13
13
  } from '@/server/usercp-actions'
14
14
 
15
- import { FormError } from '../auth/form-controls'
15
+ import { FormError, PendingButton } from '../auth/form-controls'
16
16
  import { MarkdownEditor } from '../content/markdown-editor'
17
17
  import { CustomField, type CustomFieldInput } from '../profile/custom-field'
18
18
  import { type Copy, fromCopy } from '../shell/copy'
@@ -75,9 +75,9 @@ export function ProfileForm({
75
75
  ))}
76
76
 
77
77
  <div>
78
- <button type="submit" className={BUTTON}>
78
+ <PendingButton showWorking className={BUTTON}>
79
79
  {fromCopy(copy, 'accountForm.profile.submit')}
80
- </button>
80
+ </PendingButton>
81
81
  </div>
82
82
  </form>
83
83
  )
@@ -116,9 +116,9 @@ export function DisplayGroupForm({
116
116
  </label>
117
117
 
118
118
  <div>
119
- <button type="submit" className={BUTTON}>
119
+ <PendingButton showWorking className={BUTTON}>
120
120
  {fromCopy(copy, 'accountForm.displayGroup.submit')}
121
- </button>
121
+ </PendingButton>
122
122
  </div>
123
123
  </form>
124
124
  )
@@ -214,9 +214,9 @@ export function OptionsForm({
214
214
  </label>
215
215
 
216
216
  <div>
217
- <button type="submit" className={BUTTON}>
217
+ <PendingButton showWorking className={BUTTON}>
218
218
  {fromCopy(copy, 'accountForm.options.submit')}
219
- </button>
219
+ </PendingButton>
220
220
  </div>
221
221
  </form>
222
222
  )
@@ -270,9 +270,9 @@ export function PasswordForm({ minLength, copy }: { minLength: number; copy: Cop
270
270
  <p className="text-xs text-muted-foreground">{fromCopy(copy, 'accountForm.password.note')}</p>
271
271
 
272
272
  <div>
273
- <button type="submit" className={BUTTON}>
273
+ <PendingButton showWorking className={BUTTON}>
274
274
  {fromCopy(copy, 'accountForm.password.submit')}
275
- </button>
275
+ </PendingButton>
276
276
  </div>
277
277
  </form>
278
278
  )
@@ -313,9 +313,9 @@ export function EmailForm({ email, copy }: { email: string; copy: Copy }) {
313
313
  <p className="text-xs text-muted-foreground">{fromCopy(copy, 'accountForm.email.note')}</p>
314
314
 
315
315
  <div>
316
- <button type="submit" className={BUTTON}>
316
+ <PendingButton showWorking className={BUTTON}>
317
317
  {fromCopy(copy, 'accountForm.email.submit')}
318
- </button>
318
+ </PendingButton>
319
319
  </div>
320
320
  </form>
321
321
  )
@@ -378,9 +378,9 @@ export function SignatureForm({
378
378
  />
379
379
 
380
380
  <div>
381
- <button type="submit" className={BUTTON}>
381
+ <PendingButton showWorking className={BUTTON}>
382
382
  {fromCopy(copy, 'accountForm.signature.submit')}
383
- </button>
383
+ </PendingButton>
384
384
  </div>
385
385
  </form>
386
386
  )
@@ -5,7 +5,7 @@ import { useActionState } from 'react'
5
5
  import { adminSignInAction, adminSignOutAction } from '@/server/admin-actions'
6
6
  import { EMPTY_STATE } from '@/server/auth-form-state'
7
7
 
8
- import { FormError } from '../auth/form-controls'
8
+ import { FormError, PendingButton } from '../auth/form-controls'
9
9
  import { OtpField, otpRecoveryFromCopy } from '../auth/otp-field'
10
10
  import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
11
11
 
@@ -59,12 +59,12 @@ export function AdminSignInForm({
59
59
  recovery={otpRecoveryFromCopy(copy)}
60
60
  />
61
61
 
62
- <button
63
- type="submit"
62
+ <PendingButton
63
+ showWorking
64
64
  className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
65
65
  >
66
66
  {fromCopy(copy, 'adminPanel.enter')}
67
- </button>
67
+ </PendingButton>
68
68
 
69
69
  <p className="text-xs text-muted-foreground">
70
70
  {formatFromCopy(copy, 'adminPanel.idleNote', { minutes: idleMinutes })}
@@ -76,12 +76,12 @@ export function AdminSignInForm({
76
76
  export function AdminSignOutForm({ copy }: { copy: Copy }) {
77
77
  return (
78
78
  <form action={adminSignOutAction}>
79
- <button
80
- type="submit"
79
+ <PendingButton
80
+ showWorking
81
81
  className="text-sm text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
82
82
  >
83
83
  {fromCopy(copy, 'adminPanel.leave')}
84
- </button>
84
+ </PendingButton>
85
85
  </form>
86
86
  )
87
87
  }
@@ -8,7 +8,7 @@ import { PANEL_CARD } from '@/components/shell/panel-list'
8
8
  import { EMPTY_STATE } from '@/server/auth-form-state'
9
9
  import { removeBadgeAction, saveBadgeAction } from '@/server/group-badge-actions'
10
10
 
11
- import { FormError, SubmitButton } from '../auth/form-controls'
11
+ import { FormError, PendingButton, SubmitButton } from '../auth/form-controls'
12
12
  import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
13
13
  import { Saved } from './form-bits'
14
14
 
@@ -86,9 +86,9 @@ export function BadgeUploadForm({
86
86
  <form action={removeAction}>
87
87
  <input type="hidden" name="groupId" value={groupId} />
88
88
  <input type="hidden" name="scheme" value={scheme} />
89
- <button type="submit" className={GHOST}>
89
+ <PendingButton showWorking className={GHOST}>
90
90
  {fromCopy(copy, 'admin.remove')}
91
- </button>
91
+ </PendingButton>
92
92
  </form>
93
93
  )}
94
94
  </div>
@@ -5,7 +5,7 @@ import { useActionState } from 'react'
5
5
  import { EMPTY_STATE } from '@/server/auth-form-state'
6
6
  import { addBanFilterAction, removeBanFilterAction } from '@/server/ban-filter-admin-actions'
7
7
 
8
- import { FormError, SubmitButton } from '../auth/form-controls'
8
+ import { FormError, PendingButton, SubmitButton } from '../auth/form-controls'
9
9
  import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
10
10
  import { INPUT, Saved } from './form-bits'
11
11
 
@@ -92,13 +92,13 @@ export function RemoveBanFilterForm({
92
92
  <form action={action} className="flex flex-col items-end gap-1">
93
93
  <FormError message={state.error} />
94
94
  <input type="hidden" name="id" value={id} />
95
- <button
96
- type="submit"
95
+ <PendingButton
96
+ showWorking
97
97
  className="text-xs text-destructive hover:underline"
98
98
  aria-label={formatFromCopy(copy, 'adminBanFilter.removeFilter', { pattern })}
99
99
  >
100
100
  {fromCopy(copy, 'admin.remove')}
101
- </button>
101
+ </PendingButton>
102
102
  </form>
103
103
  )
104
104
  }
@@ -6,7 +6,7 @@ import { PANEL_CARD } from '@/components/shell/panel-list'
6
6
  import { EMPTY_STATE } from '@/server/auth-form-state'
7
7
  import { removeLogoAction, saveLogoAction } from '@/server/branding-actions'
8
8
 
9
- import { FormError, SubmitButton } from '../auth/form-controls'
9
+ import { FormError, PendingButton, SubmitButton } from '../auth/form-controls'
10
10
  import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
11
11
  import { Saved } from './form-bits'
12
12
 
@@ -87,9 +87,9 @@ export function LogoUploadForm({
87
87
  {slot.src !== null && (
88
88
  <form action={removeAction}>
89
89
  <input type="hidden" name="scheme" value={slot.scheme} />
90
- <button type="submit" className={GHOST}>
90
+ <PendingButton showWorking className={GHOST}>
91
91
  {fromCopy(copy, 'admin.remove')}
92
- </button>
92
+ </PendingButton>
93
93
  </form>
94
94
  )}
95
95
  </div>