diff --git a/webapp/.env.template b/webapp/.env.template
index 76bc502f9..ee9fd0578 100644
--- a/webapp/.env.template
+++ b/webapp/.env.template
@@ -6,3 +6,6 @@ MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4
PUBLIC_REGISTRATION=false
INVITE_REGISTRATION=true
CATEGORIES_ACTIVE=false
+BADGES_ENABLED=true
+INVITE_LINK_LIMIT=7
+NETWORK_NAME="Ocelot.social"
diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss
index 4fba0b5e0..036b7b90d 100644
--- a/webapp/assets/styles/main.scss
+++ b/webapp/assets/styles/main.scss
@@ -142,6 +142,12 @@ hr {
}
}
+body.dropdown-open {
+ max-height: 100vh;
+ overflow: hidden;
+ scrollbar-gutter: stable;
+}
+
.base-card > .ds-section {
padding: 0;
margin: -$space-base;
diff --git a/webapp/components/ContentMenu/GroupContentMenu.vue b/webapp/components/ContentMenu/GroupContentMenu.vue
index e28a855ac..2c15ad28d 100644
--- a/webapp/components/ContentMenu/GroupContentMenu.vue
+++ b/webapp/components/ContentMenu/GroupContentMenu.vue
@@ -89,6 +89,11 @@ export default {
path: `/groups/edit/${this.group.id}`,
icon: 'edit',
})
+ routes.push({
+ label: this.$t('group.contentMenu.inviteLinks'),
+ path: `/groups/edit/${this.group.id}/invites`,
+ icon: 'link',
+ })
}
return routes
diff --git a/webapp/components/ContentMenu/__snapshots__/GroupContentMenu.spec.js.snap b/webapp/components/ContentMenu/__snapshots__/GroupContentMenu.spec.js.snap
index 0553dfa79..5875d2341 100644
--- a/webapp/components/ContentMenu/__snapshots__/GroupContentMenu.spec.js.snap
+++ b/webapp/components/ContentMenu/__snapshots__/GroupContentMenu.spec.js.snap
@@ -88,6 +88,27 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
+
+
+
+
diff --git a/webapp/components/InviteButton/InviteButton.spec.js b/webapp/components/InviteButton/InviteButton.spec.js
deleted file mode 100644
index 1282c2bad..000000000
--- a/webapp/components/InviteButton/InviteButton.spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { mount } from '@vue/test-utils'
-import InviteButton from './InviteButton.vue'
-
-const localVue = global.localVue
-
-const stubs = {
- 'v-popover': {
- template: '',
- },
-}
-
-describe('InviteButton.vue', () => {
- let wrapper
- let mocks
- let propsData
-
- beforeEach(() => {
- mocks = {
- $t: jest.fn(),
- navigator: {
- clipboard: {
- writeText: jest.fn(),
- },
- },
- }
- propsData = {}
- })
-
- describe('mount', () => {
- const Wrapper = () => {
- return mount(InviteButton, { mocks, localVue, propsData, stubs })
- }
-
- beforeEach(() => {
- wrapper = Wrapper()
- })
-
- it('renders', () => {
- expect(wrapper.find('.invite-button').exists()).toBe(true)
- })
-
- it('open popup', () => {
- wrapper.find('.base-button').trigger('click')
- expect(wrapper.find('.invite-button').exists()).toBe(true)
- })
-
- it('invite codes not available', async () => {
- wrapper.find('.base-button').trigger('click') // open popup
- wrapper.find('.invite-button').trigger('click') // click copy button
- expect(mocks.$t).toHaveBeenCalledWith('invite-codes.not-available')
- })
-
- it.skip('invite codes copied to clipboard', async () => {
- wrapper.find('.base-button').trigger('click') // open popup
- wrapper.find('.invite-button').trigger('click') // click copy button
- expect(mocks.$t).toHaveBeenCalledWith('invite-codes.not-available')
- })
- })
-})
diff --git a/webapp/components/InviteButton/InviteButton.vue b/webapp/components/InviteButton/InviteButton.vue
index 3042a706a..3eea98f74 100644
--- a/webapp/components/InviteButton/InviteButton.vue
+++ b/webapp/components/InviteButton/InviteButton.vue
@@ -13,24 +13,18 @@
/>
-
@@ -3009,6 +3030,27 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
+
+
+
+
@@ -6489,6 +6531,27 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
+
+
+
+
diff --git a/webapp/pages/groups/edit/_id.vue b/webapp/pages/groups/edit/_id.vue
index 57c7d9f6a..66eecd364 100644
--- a/webapp/pages/groups/edit/_id.vue
+++ b/webapp/pages/groups/edit/_id.vue
@@ -13,7 +13,7 @@
-
+
@@ -39,9 +39,18 @@ export default {
name: this.$t('group.members'),
path: `/groups/edit/${this.group.id}/members`,
},
+ {
+ name: this.$t('group.invite-links'),
+ path: `/groups/edit/${this.group.id}/invites`,
+ },
]
},
},
+ data() {
+ return {
+ group: {},
+ }
+ },
async asyncData(context) {
const {
app,
@@ -62,5 +71,10 @@ export default {
}
return { group }
},
+ methods: {
+ updateInviteCodes(inviteCodes) {
+ this.group.inviteCodes = inviteCodes
+ },
+ },
}
diff --git a/webapp/pages/groups/edit/_id/__snapshots__/invites.spec.js.snap b/webapp/pages/groups/edit/_id/__snapshots__/invites.spec.js.snap
new file mode 100644
index 000000000..2d1155691
--- /dev/null
+++ b/webapp/pages/groups/edit/_id/__snapshots__/invites.spec.js.snap
@@ -0,0 +1,167 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`invites.vue renders 1`] = `
+
+
+
+
+ invite-codes.group-invite-links
+
+
+
+
+
+
+
+ -
+
+
+
+ INVITE1
+
+
+ —
+
+
+
+
+
+
+
+
+ invite-codes.redeemed-count-0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ invite-codes.generate-code-explanation
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/webapp/pages/groups/edit/_id/invites.spec.js b/webapp/pages/groups/edit/_id/invites.spec.js
new file mode 100644
index 000000000..8c163a4e9
--- /dev/null
+++ b/webapp/pages/groups/edit/_id/invites.spec.js
@@ -0,0 +1,86 @@
+import { render, screen, fireEvent } from '@testing-library/vue'
+
+import invites from './invites.vue'
+
+const localVue = global.localVue
+
+describe('invites.vue', () => {
+ let wrapper
+ let mocks
+
+ beforeEach(() => {
+ mocks = {
+ $t: jest.fn((v) => v),
+ $apollo: {
+ mutate: jest.fn(),
+ },
+ $env: {
+ NETWORK_NAME: 'test-network',
+ INVITE_LINK_LIMIT: 5,
+ },
+ $toast: {
+ success: jest.fn(),
+ error: jest.fn(),
+ },
+ localVue,
+ }
+ })
+
+ const Wrapper = () => {
+ return render(invites, {
+ localVue,
+ propsData: {
+ group: {
+ id: 'group1',
+ name: 'Group 1',
+ inviteCodes: [
+ {
+ code: 'INVITE1',
+ comment: 'Test invite 1',
+ redeemedByCount: 0,
+ isValid: true,
+ },
+ {
+ code: 'INVITE2',
+ comment: 'Test invite 2',
+ redeemedByCount: 1,
+ isValid: false,
+ },
+ ],
+ },
+ },
+ mocks,
+ stubs: {
+ 'client-only': true,
+ },
+ })
+ }
+
+ it('renders', () => {
+ wrapper = Wrapper()
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ describe('when a new invite code is generated', () => {
+ beforeEach(async () => {
+ wrapper = Wrapper()
+ const createButton = screen.getByLabelText('invite-codes.generate-code')
+ await fireEvent.click(createButton)
+ })
+
+ it('calls the mutation to generate a new invite code', () => {
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: expect.anything(),
+ update: expect.anything(),
+ variables: {
+ groupId: 'group1',
+ comment: '',
+ },
+ })
+ })
+
+ it('shows a success message', () => {
+ expect(mocks.$toast.success).toHaveBeenCalledWith('invite-codes.create-success')
+ })
+ })
+})
diff --git a/webapp/pages/groups/edit/_id/invites.vue b/webapp/pages/groups/edit/_id/invites.vue
new file mode 100644
index 000000000..a19cdbf40
--- /dev/null
+++ b/webapp/pages/groups/edit/_id/invites.vue
@@ -0,0 +1,81 @@
+
+
+
+ {{ $t('invite-codes.group-invite-links') }}
+
+
+
+
+
+
+
diff --git a/webapp/store/auth.js b/webapp/store/auth.js
index 4ef63e3ea..0633c6e1e 100644
--- a/webapp/store/auth.js
+++ b/webapp/store/auth.js
@@ -18,6 +18,9 @@ export const mutations = {
SET_USER(state, user) {
state.user = user || null
},
+ SET_USER_PARTIAL(state, user) {
+ state.user = { ...state.user, ...user }
+ },
SET_TOKEN(state, token) {
state.token = token || null
},