diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js
new file mode 100644
index 000000000..8baddc692
--- /dev/null
+++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js
@@ -0,0 +1,46 @@
+import { render, fireEvent, screen } from '@testing-library/vue'
+import BadgesSection from './BadgesSection.vue'
+
+const localVue = global.localVue
+
+const badge1 = {
+ id: 'badge1',
+ icon: 'icon1',
+ type: 'type1',
+ description: 'description1',
+ isActive: true,
+}
+const badge2 = {
+ id: 'badge2',
+ icon: 'icon2',
+ type: 'type1',
+ description: 'description2',
+ isActive: false,
+}
+
+describe('Admin/BadgesSection', () => {
+ let wrapper
+
+ const Wrapper = () => {
+ return render(BadgesSection, {
+ localVue,
+ propsData: {
+ badges: [badge1, badge2],
+ },
+ })
+ }
+
+ beforeEach(() => {
+ wrapper = Wrapper()
+ })
+
+ it('renders', () => {
+ expect(wrapper.baseElement).toMatchSnapshot()
+ })
+
+ it('emits toggleButton', async () => {
+ const button = screen.getByAltText(badge1.description)
+ await fireEvent.click(button)
+ expect(wrapper.emitted().toggleBadge[0][0]).toEqual(badge1)
+ })
+})
diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.vue b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue
new file mode 100644
index 000000000..8ff9da7ed
--- /dev/null
+++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue
@@ -0,0 +1,55 @@
+
+
+
{{ title }}
+
+
+
+
+
+
+
+
+
diff --git a/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap
new file mode 100644
index 000000000..c09a50725
--- /dev/null
+++ b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Admin/BadgesSection renders 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js
index 8ad247ad1..147e93c6f 100644
--- a/webapp/graphql/User.js
+++ b/webapp/graphql/User.js
@@ -90,6 +90,23 @@ export const adminUserQuery = () => {
`
}
+export const adminUserBadgesQuery = () => {
+ return gql`
+ query User($id: ID!) {
+ User(id: $id) {
+ id
+ name
+ badgeTrophies {
+ id
+ }
+ badgeVerification {
+ id
+ }
+ }
+ }
+ `
+}
+
export const mapUserQuery = (i18n) => {
const lang = i18n.locale().toUpperCase()
return gql`
diff --git a/webapp/graphql/admin/Badges.js b/webapp/graphql/admin/Badges.js
new file mode 100644
index 000000000..2c037f2f3
--- /dev/null
+++ b/webapp/graphql/admin/Badges.js
@@ -0,0 +1,54 @@
+import gql from 'graphql-tag'
+
+export const queryBadges = () => gql`
+ query {
+ Badge {
+ id
+ type
+ icon
+ description
+ }
+ }
+`
+
+export const setVerificationBadge = () => gql`
+ mutation ($badgeId: ID!, $userId: ID!) {
+ setVerificationBadge(badgeId: $badgeId, userId: $userId) {
+ id
+ badgeVerification {
+ id
+ }
+ badgeTrophies {
+ id
+ }
+ }
+ }
+`
+
+export const rewardTrophyBadge = () => gql`
+ mutation ($badgeId: ID!, $userId: ID!) {
+ rewardTrophyBadge(badgeId: $badgeId, userId: $userId) {
+ id
+ badgeVerification {
+ id
+ }
+ badgeTrophies {
+ id
+ }
+ }
+ }
+`
+
+export const revokeBadge = () => gql`
+ mutation ($badgeId: ID!, $userId: ID!) {
+ revokeBadge(badgeId: $badgeId, userId: $userId) {
+ id
+ badgeVerification {
+ id
+ }
+ badgeTrophies {
+ id
+ }
+ }
+ }
+`
diff --git a/webapp/locales/de.json b/webapp/locales/de.json
index 19d0896a9..ce122672d 100644
--- a/webapp/locales/de.json
+++ b/webapp/locales/de.json
@@ -10,6 +10,28 @@
"saveCategories": "Themen speichern"
},
"admin": {
+ "badges": {
+ "description": "Stelle die verfügbaren Auszeichnungen für diesen Nutzer ein.",
+ "revokeTrophy": {
+ "error": "Trophäe konnte nicht widerrufen werden!",
+ "success": "Trophäe erfolgreich widerrufen"
+ },
+ "revokeVerification": {
+ "error": "Verifizierung konnte nicht gesetzt werden!",
+ "success": "Verifizierung erfolgreich widerrufen"
+ },
+ "rewardTrophy": {
+ "error": "Trophäe konnte nicht vergeben werden!",
+ "success": "Trophäe erfolgreich vergeben!"
+ },
+ "setVerification": {
+ "error": "Verifizierung konnte nicht gesetzt werden!",
+ "success": "Verifizierung erfolgreich gesetzt"
+ },
+ "title": "Auszeichnungen",
+ "trophyBadges": "Trophäen",
+ "verificationBadges": "Verifizierungen"
+ },
"categories": {
"categoryName": "Name",
"name": "Themen",
@@ -68,13 +90,15 @@
"roleChanged": "Rolle erfolgreich geändert!",
"table": {
"columns": {
+ "badges": "Auszeichnungen",
"createdAt": "Erstellt am",
"email": "E-Mail",
"name": "Name",
"number": "Nr.",
"role": "Rolle",
"slug": "Alias"
- }
+ },
+ "edit": "Bearbeiten"
}
}
},
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index b4c1125f3..f178da549 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -10,6 +10,28 @@
"saveCategories": "Save topics"
},
"admin": {
+ "badges": {
+ "description": "Configure the available badges for this user",
+ "revokeTrophy": {
+ "error": "Trophy could not be revoked!",
+ "success": "Trophy successfully revoked!"
+ },
+ "revokeVerification": {
+ "error": "Verification could not be revoked!",
+ "success": "Verification succesfully revoked"
+ },
+ "rewardTrophy": {
+ "error": "Trophy could not be rewarded!",
+ "success": "Trophy successfully rewarded!"
+ },
+ "setVerification": {
+ "error": "Verification could not be set!",
+ "success": "Verification successfully set!"
+ },
+ "title": "Badges",
+ "trophyBadges": "Trophies",
+ "verificationBadges": "Verifications"
+ },
"categories": {
"categoryName": "Name",
"name": "Topics",
@@ -68,13 +90,15 @@
"roleChanged": "Role changed successfully!",
"table": {
"columns": {
+ "badges": "Badges",
"createdAt": "Created at",
"email": "E-mail",
"name": "Name",
"number": "No.",
"role": "Role",
"slug": "Slug"
- }
+ },
+ "edit": "Edit"
}
}
},
diff --git a/webapp/locales/es.json b/webapp/locales/es.json
index 7184a327a..31f2cc5f4 100644
--- a/webapp/locales/es.json
+++ b/webapp/locales/es.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Nombre",
"name": "Categorías",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": "Creado el",
"email": "Correo electrónico",
"name": "Nombre",
"number": "No.",
"role": "Rol",
"slug": "Alias"
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json
index 851743e63..4bbca2b82 100644
--- a/webapp/locales/fr.json
+++ b/webapp/locales/fr.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Nom",
"name": "Catégories",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": "Créé à",
"email": "Mail",
"name": "Nom",
"number": "Num.",
"role": "Rôle",
"slug": "Slug"
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/it.json b/webapp/locales/it.json
index 0c693ca43..21bfaa859 100644
--- a/webapp/locales/it.json
+++ b/webapp/locales/it.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Nome",
"name": "Categorie",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json
index 433adf8e8..f67518c21 100644
--- a/webapp/locales/nl.json
+++ b/webapp/locales/nl.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Naam",
"name": "Categorieën",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json
index c0ab9d09c..4c6a96a5f 100644
--- a/webapp/locales/pl.json
+++ b/webapp/locales/pl.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Nazwa",
"name": "Kategorie",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json
index 02f8fb2cc..7d5ad52c1 100644
--- a/webapp/locales/pt.json
+++ b/webapp/locales/pt.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Nome",
"name": "Categorias",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": "Criado em",
"email": "E-mail",
"name": "Nome",
"number": "N.º",
"role": "Função",
"slug": "Slug"
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json
index ea0279450..3a394d6ff 100644
--- a/webapp/locales/ru.json
+++ b/webapp/locales/ru.json
@@ -10,6 +10,28 @@
"saveCategories": null
},
"admin": {
+ "badges": {
+ "description": null,
+ "revokeTrophy": {
+ "error": null,
+ "success": null
+ },
+ "revokeVerification": {
+ "error": null,
+ "success": null
+ },
+ "rewardTrophy": {
+ "error": null,
+ "success": null
+ },
+ "setVerification": {
+ "error": null,
+ "success": null
+ },
+ "title": null,
+ "trophyBadges": null,
+ "verificationBadges": null
+ },
"categories": {
"categoryName": "Имя",
"name": "Категории",
@@ -68,13 +90,15 @@
"roleChanged": null,
"table": {
"columns": {
+ "badges": null,
"createdAt": "Дата создания",
"email": "Эл. почта",
"name": "Имя",
"number": "№",
"role": "Роль",
"slug": "Алиас"
- }
+ },
+ "edit": null
}
}
},
diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js
index b3bbdfc2d..1c963615a 100644
--- a/webapp/nuxt.config.js
+++ b/webapp/nuxt.config.js
@@ -207,6 +207,15 @@ export default {
'X-API-TOKEN': CONFIG.BACKEND_TOKEN,
},
},
+ '/img': {
+ // make this configurable (nuxt-dotenv)
+ target: CONFIG.GRAPHQL_URI,
+ toProxy: true, // cloudflare needs that
+ headers: {
+ 'X-UI-Request': true,
+ 'X-API-TOKEN': CONFIG.BACKEND_TOKEN,
+ },
+ },
},
// Give apollo module options
diff --git a/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap b/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap
new file mode 100644
index 000000000..2c5ddc686
--- /dev/null
+++ b/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`.vue renders 1`] = `
+
+
+
+
+
+
+
+
+ User1
+ -
+ admin.badges.title
+
+
+
+
+ admin.badges.description
+
+
+
+
+
+
+ admin.badges.verificationBadges
+
+
+
+
+
+
+
+
+
+
+ admin.badges.trophyBadges
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/webapp/pages/admin/users/_id.spec.js b/webapp/pages/admin/users/_id.spec.js
new file mode 100644
index 000000000..933de58de
--- /dev/null
+++ b/webapp/pages/admin/users/_id.spec.js
@@ -0,0 +1,326 @@
+import { render, fireEvent, screen } from '@testing-library/vue'
+import BadgesPage from './_id.vue'
+
+const localVue = global.localVue
+
+const availableBadges = [
+ {
+ id: 'verification-badge-1',
+ icon: 'icon1',
+ type: 'verification',
+ description: 'description-v-1',
+ },
+ {
+ id: 'verification-badge-2',
+ icon: 'icon2',
+ type: 'verification',
+ description: 'description-v-2',
+ },
+ {
+ id: 'trophy-badge-1',
+ icon: 'icon3',
+ type: 'trophy',
+ description: 'description-t-1',
+ },
+ {
+ id: 'trophy-badge-2',
+ icon: 'icon4',
+ type: 'trophy',
+ description: 'description-t-2',
+ },
+]
+
+const user = {
+ id: 'user1',
+ name: 'User1',
+ badgeVerification: {
+ id: 'verification-badge-1',
+ },
+ badgeTrophies: [
+ {
+ id: 'trophy-badge-2',
+ },
+ ],
+}
+
+describe('.vue', () => {
+ let wrapper
+ let mocks
+
+ beforeEach(() => {
+ mocks = {
+ $t: jest.fn((v) => v),
+ $apollo: {
+ User: {
+ query: jest.fn(),
+ },
+ badges: {
+ query: jest.fn(),
+ },
+ mutate: jest.fn(),
+ },
+ $toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+ }
+ })
+ const Wrapper = () => {
+ return render(BadgesPage, {
+ mocks,
+ localVue,
+ data: () => ({
+ user,
+ badges: availableBadges,
+ }),
+ })
+ }
+
+ beforeEach(() => {
+ wrapper = Wrapper()
+ })
+
+ it('renders', () => {
+ expect(wrapper.baseElement).toMatchSnapshot()
+ })
+
+ describe('after clicking an inactive verification badge', () => {
+ let button
+ beforeEach(() => {
+ button = screen.getByAltText(availableBadges[1].description)
+ })
+
+ describe('and successful server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockResolvedValue({
+ data: {
+ setVerificationBadge: {
+ id: 'user1',
+ badgeVerification: {
+ id: availableBadges[1].id,
+ },
+ badgeTrophies: [],
+ },
+ },
+ })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[1].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows success message', async () => {
+ expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.setVerification.success')
+ })
+ })
+
+ describe('and failed server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[1].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows error message', async () => {
+ expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.setVerification.error')
+ })
+ })
+
+ describe('after clicking an inactive trophy badge', () => {
+ let button
+ beforeEach(() => {
+ button = screen.getByAltText(availableBadges[2].description)
+ })
+
+ describe('and successful server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockResolvedValue({
+ data: {
+ setTrophyBadge: {
+ id: 'user1',
+ badgeVerification: null,
+ badgeTrophies: [
+ {
+ id: availableBadges[2].id,
+ },
+ ],
+ },
+ },
+ })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[2].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows success message', async () => {
+ expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.rewardTrophy.success')
+ })
+ })
+
+ describe('and failed server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[2].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows error message', async () => {
+ expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.rewardTrophy.error')
+ })
+ })
+ })
+
+ describe('after clicking an active verification badge', () => {
+ let button
+ beforeEach(() => {
+ button = screen.getByAltText(availableBadges[0].description)
+ })
+
+ describe('and successful server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockResolvedValue({
+ data: {
+ setVerificationBadge: {
+ id: 'user1',
+ badgeVerification: null,
+ badgeTrophies: [],
+ },
+ },
+ })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[0].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows success message', async () => {
+ expect(mocks.$toast.success).toHaveBeenCalledWith(
+ 'admin.badges.revokeVerification.success',
+ )
+ })
+ })
+
+ describe('and failed server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[0].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows error message', async () => {
+ expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.revokeVerification.error')
+ })
+ })
+ })
+ })
+
+ describe('after clicking an active trophy badge', () => {
+ let button
+ beforeEach(() => {
+ button = screen.getByAltText(availableBadges[3].description)
+ })
+
+ describe('and successful server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockResolvedValue({
+ data: {
+ setTrophyBadge: {
+ id: 'user1',
+ badgeVerification: null,
+ badgeTrophies: [
+ {
+ id: availableBadges[3].id,
+ },
+ ],
+ },
+ },
+ })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[3].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows success message', async () => {
+ expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.revokeTrophy.success')
+ })
+ })
+
+ describe('and failed server response', () => {
+ beforeEach(async () => {
+ mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' })
+ await fireEvent.click(button)
+ })
+
+ it('calls the mutation', async () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ variables: {
+ badgeId: availableBadges[3].id,
+ userId: 'user1',
+ },
+ })
+ })
+
+ it('shows error message', async () => {
+ expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.revokeTrophy.error')
+ })
+ })
+ })
+})
diff --git a/webapp/pages/admin/users/_id.vue b/webapp/pages/admin/users/_id.vue
new file mode 100644
index 000000000..808e1653a
--- /dev/null
+++ b/webapp/pages/admin/users/_id.vue
@@ -0,0 +1,163 @@
+
+
+
+
+ {{ user && user.name }}
+ -
+ {{ $t('admin.badges.title') }}
+
+ {{ $t('admin.badges.description') }}
+
+
+
+
+
+
+
+
+
diff --git a/webapp/pages/admin/users.spec.js b/webapp/pages/admin/users/index.spec.js
similarity index 99%
rename from webapp/pages/admin/users.spec.js
rename to webapp/pages/admin/users/index.spec.js
index 43c51fb52..8d6b923c5 100644
--- a/webapp/pages/admin/users.spec.js
+++ b/webapp/pages/admin/users/index.spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils'
import Vuex from 'vuex'
-import Users from './users.vue'
+import Users from './index.vue'
const localVue = global.localVue
diff --git a/webapp/pages/admin/users.vue b/webapp/pages/admin/users/index.vue
similarity index 93%
rename from webapp/pages/admin/users.vue
rename to webapp/pages/admin/users/index.vue
index 44f162c77..24258a57f 100644
--- a/webapp/pages/admin/users.vue
+++ b/webapp/pages/admin/users/index.vue
@@ -63,6 +63,16 @@
{{ scope.row.role }}
+
+
+ {{ $t('admin.users.table.edit') }}
+
+
@@ -132,6 +142,10 @@ export default {
label: this.$t('admin.users.table.columns.role'),
align: 'right',
},
+ badges: {
+ label: this.$t('admin.users.table.columns.badges'),
+ align: 'right',
+ },
}
},
},