feat(webapp): more button icons, more loading states (#9243)

This commit is contained in:
Ulf Gebhardt 2026-02-18 03:59:10 +01:00 committed by GitHub
parent 82d2a2b1f3
commit c0a7965d24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 85 additions and 19 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>lock</title>
<path d="M15 3c3.845 0 7 3.155 7 7v3h3v16h-20v-16h3v-3c0-3.845 3.155-7 7-7zM15 5c-2.755 0-5 2.245-5 5v3h10v-3c0-2.755-2.245-5-5-5zM7 15v12h16v-12h-16z"></path>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@ -20,6 +20,9 @@
:loading="loading" :loading="loading"
:disabled="disabled || !!errors" :disabled="disabled || !!errors"
> >
<template #icon>
<os-icon :icon="icons.comment" />
</template>
{{ $t('post.comment.submit') }} {{ $t('post.comment.submit') }}
</os-button> </os-button>
</div> </div>
@ -29,7 +32,8 @@
</template> </template>
<script> <script>
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import HcEditor from '~/components/Editor/Editor' import HcEditor from '~/components/Editor/Editor'
import { COMMENT_MIN_LENGTH } from '~/constants/comment' import { COMMENT_MIN_LENGTH } from '~/constants/comment'
import { minimisedUserQuery } from '~/graphql/User' import { minimisedUserQuery } from '~/graphql/User'
@ -38,6 +42,7 @@ import CommentMutations from '~/graphql/CommentMutations'
export default { export default {
components: { components: {
OsButton, OsButton,
OsIcon,
HcEditor, HcEditor,
}, },
props: { props: {
@ -48,6 +53,9 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
created() {
this.icons = iconRegistry
},
data() { data() {
return { return {
disabled: true, disabled: true,

View File

@ -172,14 +172,13 @@ describe('DeleteData.vue', () => {
describe('error handling', () => { describe('error handling', () => {
it('shows an error toaster when the mutation rejects', async () => { it('shows an error toaster when the mutation rejects', async () => {
mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Not Authorized!' })
enableDeletionInput = wrapper.find('.ds-input') enableDeletionInput = wrapper.find('.ds-input')
enableDeletionInput.setValue(deleteAccountName) enableDeletionInput.setValue(deleteAccountName)
await Vue.nextTick() await Vue.nextTick()
deleteAccountBtn = wrapper.find('[data-test="delete-button"]') deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
await deleteAccountBtn.trigger('click') await deleteAccountBtn.trigger('click')
// second submission causes mutation to reject await flushPromises()
await deleteAccountBtn.trigger('click')
await mocks.$apollo.mutate
expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!') expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!')
}) })
}) })

View File

@ -46,6 +46,7 @@
variant="danger" variant="danger"
appearance="filled" appearance="filled"
:disabled="!deleteEnabled" :disabled="!deleteEnabled"
:loading="loading"
data-test="delete-button" data-test="delete-button"
@click="handleSubmit" @click="handleSubmit"
> >
@ -73,6 +74,7 @@ export default {
deleteComments: false, deleteComments: false,
enableDeletionValue: null, enableDeletionValue: null,
currentUserCounts: {}, currentUserCounts: {},
loading: false,
} }
}, },
apollo: { apollo: {
@ -101,6 +103,7 @@ export default {
logout: 'auth/logout', logout: 'auth/logout',
}), }),
handleSubmit() { handleSubmit() {
this.loading = true
const resourceArgs = [] const resourceArgs = []
if (this.deleteContributions) { if (this.deleteContributions) {
resourceArgs.push('Post') resourceArgs.push('Post')
@ -130,6 +133,7 @@ export default {
}) })
.catch((error) => { .catch((error) => {
this.$toast.error(error.message) this.$toast.error(error.message)
this.loading = false
}) })
}, },
}, },

View File

@ -3,19 +3,24 @@
<progress-bar :label="label" :goal="goal" :progress="progress"> <progress-bar :label="label" :goal="goal" :progress="progress">
<os-button size="sm" variant="primary" @click="redirectToPage(links.DONATE)"> <os-button size="sm" variant="primary" @click="redirectToPage(links.DONATE)">
{{ $t('donations.donate-now') }} {{ $t('donations.donate-now') }}
<template #suffix>
<os-icon :icon="icons.heartO" />
</template>
</os-button> </os-button>
</progress-bar> </progress-bar>
</div> </div>
</template> </template>
<script> <script>
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import links from '~/constants/links.js' import links from '~/constants/links.js'
import ProgressBar from '~/components/ProgressBar/ProgressBar.vue' import ProgressBar from '~/components/ProgressBar/ProgressBar.vue'
export default { export default {
components: { components: {
OsButton, OsButton,
OsIcon,
ProgressBar, ProgressBar,
}, },
props: { props: {
@ -23,6 +28,9 @@ export default {
goal: { type: Number, required: true }, goal: { type: Number, required: true },
progress: { type: Number, required: true }, progress: { type: Number, required: true },
}, },
created() {
this.icons = iconRegistry
},
data() { data() {
return { return {
links, links,

View File

@ -63,6 +63,9 @@
userId = scope.row.user.id userId = scope.row.user.id
" "
> >
<template #icon>
<os-icon :icon="icons.userTimes" />
</template>
{{ $t('group.removeMemberButton') }} {{ $t('group.removeMemberButton') }}
</os-button> </os-button>
</template> </template>
@ -80,7 +83,8 @@
</div> </div>
</template> </template>
<script> <script>
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js' import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar' import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
@ -88,6 +92,7 @@ export default {
name: 'GroupMember', name: 'GroupMember',
components: { components: {
OsButton, OsButton,
OsIcon,
ProfileAvatar, ProfileAvatar,
}, },
props: { props: {
@ -100,6 +105,9 @@ export default {
required: false, required: false,
}, },
}, },
created() {
this.icons = iconRegistry
},
data() { data() {
return { return {
id: 'search-user-to-add-to-group', id: 'search-user-to-add-to-group',

View File

@ -51,14 +51,20 @@
appearance="ghost" appearance="ghost"
variant="primary" variant="primary"
> >
<template #icon>
<os-icon :icon="icons.bell" />
</template>
{{ $t('notifications.pageLink') }} {{ $t('notifications.pageLink') }}
</os-button> </os-button>
<os-button <os-button
appearance="ghost" appearance="ghost"
variant="primary" variant="primary"
@click="markAllAsRead(closeMenu)" @click="markAllAsRead()"
data-test="markAllAsRead-button" data-test="markAllAsRead-button"
> >
<template #icon>
<os-icon :icon="icons.check" />
</template>
{{ $t('notifications.markAllAsRead') }} {{ $t('notifications.markAllAsRead') }}
</os-button> </os-button>
</ds-flex-item> </ds-flex-item>
@ -77,7 +83,7 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import unionBy from 'lodash/unionBy' import unionBy from 'lodash/unionBy'
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import { import {
notificationQuery, notificationQuery,
@ -96,6 +102,7 @@ export default {
CounterIcon, CounterIcon,
Dropdown, Dropdown,
OsButton, OsButton,
OsIcon,
}, },
data() { data() {
return { return {
@ -132,12 +139,11 @@ export default {
this.$toast.error(error.message) this.$toast.error(error.message)
} }
}, },
async markAllAsRead(closeMenu) { async markAllAsRead() {
if (!this.hasNotifications) { if (!this.hasNotifications) {
return return
} }
closeMenu?.()
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: markAllAsReadMutation(this.$i18n), mutation: markAllAsReadMutation(this.$i18n),

View File

@ -31,6 +31,9 @@
:disabled="!!errors" :disabled="!!errors"
type="submit" type="submit"
> >
<template #icon>
<os-icon :icon="icons.lock" />
</template>
{{ $t('settings.security.change-password.button') }} {{ $t('settings.security.change-password.button') }}
</os-button> </os-button>
</ds-space> </ds-space>
@ -39,7 +42,8 @@
</template> </template>
<script> <script>
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import PasswordStrength from './Strength' import PasswordStrength from './Strength'
import PasswordForm from '~/components/utils/PasswordFormHelper' import PasswordForm from '~/components/utils/PasswordFormHelper'
@ -48,8 +52,12 @@ export default {
name: 'ChangePassword', name: 'ChangePassword',
components: { components: {
OsButton, OsButton,
OsIcon,
PasswordStrength, PasswordStrength,
}, },
created() {
this.icons = iconRegistry
},
data() { data() {
const passwordForm = PasswordForm({ translate: this.$t }) const passwordForm = PasswordForm({ translate: this.$t })
return { return {

View File

@ -31,6 +31,9 @@
:disabled="!!errors" :disabled="!!errors"
type="submit" type="submit"
> >
<template #icon>
<os-icon :icon="icons.lock" />
</template>
{{ $t('settings.security.change-password.button') }} {{ $t('settings.security.change-password.button') }}
</os-button> </os-button>
</ds-space> </ds-space>
@ -67,7 +70,8 @@
</template> </template>
<script> <script>
import { OsButton } from '@ocelot-social/ui' import { OsButton, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import emails from '../../constants/emails.js' import emails from '../../constants/emails.js'
import PasswordStrength from '../Password/Strength' import PasswordStrength from '../Password/Strength'
import gql from 'graphql-tag' import gql from 'graphql-tag'
@ -77,6 +81,7 @@ import PasswordForm from '~/components/utils/PasswordFormHelper'
export default { export default {
components: { components: {
OsButton, OsButton,
OsIcon,
SweetalertIcon, SweetalertIcon,
PasswordStrength, PasswordStrength,
}, },
@ -84,6 +89,9 @@ export default {
email: { type: String, required: true }, email: { type: String, required: true },
nonce: { type: String, required: true }, nonce: { type: String, required: true },
}, },
created() {
this.icons = iconRegistry
},
data() { data() {
const passwordForm = PasswordForm({ translate: this.$t }) const passwordForm = PasswordForm({ translate: this.$t })
return { return {

View File

@ -18,6 +18,9 @@
@click="showFiledReports = !showFiledReports" @click="showFiledReports = !showFiledReports"
> >
{{ $t('moderation.reports.moreDetails') }} {{ $t('moderation.reports.moreDetails') }}
<template #suffix>
<os-icon :icon="showFiledReports ? icons.angleUp : icons.angleDown" />
</template>
</os-button> </os-button>
</td> </td>

View File

@ -59,6 +59,7 @@
appearance="outline" appearance="outline"
circle circle
size="sm" size="sm"
:loading="unblockingUserId === scope.row.id"
:aria-label="$t('settings.blocked-users.columns.unblock')" :aria-label="$t('settings.blocked-users.columns.unblock')"
@click="unblockUser(scope)" @click="unblockUser(scope)"
> >
@ -102,6 +103,7 @@ export default {
data() { data() {
return { return {
blockedUsers: [], blockedUsers: [],
unblockingUserId: null,
} }
}, },
computed: { computed: {
@ -119,13 +121,20 @@ export default {
}, },
methods: { methods: {
async unblockUser(user) { async unblockUser(user) {
await this.$apollo.mutate({ this.unblockingUserId = user.row.id
mutation: unblockUser(), try {
variables: { id: user.row.id }, await this.$apollo.mutate({
}) mutation: unblockUser(),
this.$apollo.queries.blockedUsers.refetch() variables: { id: user.row.id },
const { name } = user.row })
this.$toast.success(this.$t('settings.blocked-users.unblocked', { name })) this.$apollo.queries.blockedUsers.refetch()
const { name } = user.row
this.$toast.success(this.$t('settings.blocked-users.unblocked', { name }))
} catch (error) {
this.$toast.error(error.message)
} finally {
this.unblockingUserId = null
}
}, },
}, },
} }