diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js index 16df63a48..0d61a041a 100644 --- a/backend/src/constants/categories.js +++ b/backend/src/constants/categories.js @@ -4,7 +4,7 @@ export const CATEGORIES_MAX = 3 export const categories = [ { - icon: 'users', + icon: 'networking', name: 'networking', description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe', }, @@ -14,12 +14,12 @@ export const categories = [ description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten', }, { - icon: 'lightbulb', + icon: 'energy', name: 'energy', description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...', }, { - icon: 'smile', + icon: 'psyche', name: 'psyche', description: 'Seele, Gefühle, Glück', }, @@ -34,7 +34,7 @@ export const categories = [ description: 'Menschenrechte, Gesetze, Verordnungen', }, { - icon: 'money', + icon: 'finance', name: 'finance', description: 'Geld, Finanzsystem, Alternativwährungen, ...', }, @@ -44,7 +44,7 @@ export const categories = [ description: 'Familie, Pädagogik, Schule, Prägung', }, { - icon: 'suitcase', + icon: 'mobility', name: 'mobility', description: 'Reise, Verkehr, Elektromobilität', }, @@ -54,48 +54,48 @@ export const categories = [ description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...', }, { - icon: 'angellist', + icon: 'peace', name: 'peace', description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken', }, { - icon: 'university', + icon: 'politics', name: 'politics', description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien', }, { - icon: 'tree', + icon: 'nature', name: 'nature', description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt', }, { - icon: 'graduation-cap', + icon: 'science', name: 'science', description: 'Bildung, Hochschule, Publikationen, ...', }, { - icon: 'medkit', + icon: 'health', name: 'health', description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...', }, { - icon: 'desktop', + icon: 'media', name: 'it-and-media', description: 'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps', }, { - icon: 'heart-o', + icon: 'spirituality', name: 'spirituality', description: 'Religion, Werte, Ethik', }, { - icon: 'music', + icon: 'culture', name: 'culture', description: 'Kunst, Theater, Musik, Fotografie, Film', }, { - icon: 'ellipsis-h', + icon: 'miscellaneous', name: 'miscellaneous', description: '', }, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 6e942d60a..9cb15d69f 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -4,7 +4,10 @@ import CONFIG from '../../config' import { CATEGORIES_MIN, CATEGORIES_MAX } from '../../constants/categories' import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups' import { removeHtmlTags } from '../../middleware/helpers/cleanHtml.js' -import Resolver from './helpers/Resolver' +import Resolver, { + removeUndefinedNullValuesFromObject, + convertObjectToCypherMapLiteral, +} from './helpers/Resolver' import { mergeImage } from './images/images' export default { @@ -12,20 +15,10 @@ export default { Group: async (_object, params, context, _resolveInfo) => { const { isMember, id, slug } = params const matchParams = { id, slug } - Object.keys(matchParams).forEach((key) => { - if ([undefined, null].includes(matchParams[key])) { - delete matchParams[key] - } - }) + removeUndefinedNullValuesFromObject(matchParams) const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (txc) => { - const matchParamsEntries = Object.entries(matchParams) - let groupMatchParamsCypher = '' - matchParamsEntries.forEach((ele, index) => { - groupMatchParamsCypher += index === 0 ? ' {' : '' - groupMatchParamsCypher += `${ele[0]}: "${ele[1]}"` - groupMatchParamsCypher += index < matchParamsEntries.length - 1 ? ', ' : '}' - }) + const groupMatchParamsCypher = convertObjectToCypherMapLiteral(matchParams) let groupCypher if (isMember === true) { groupCypher = ` diff --git a/backend/src/schema/resolvers/helpers/Resolver.js b/backend/src/schema/resolvers/helpers/Resolver.js index f2861e7a0..56d382690 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.js +++ b/backend/src/schema/resolvers/helpers/Resolver.js @@ -121,3 +121,24 @@ export default function Resolver(type, options = {}) { } return result } + +export const removeUndefinedNullValuesFromObject = (obj) => { + Object.keys(obj).forEach((key) => { + if ([undefined, null].includes(obj[key])) { + delete obj[key] + } + }) +} + +export const convertObjectToCypherMapLiteral = (params) => { + // I have found no other way yet. maybe "apoc.convert.fromJsonMap(key)" can help, but couldn't get it how, see: https://stackoverflow.com/questions/43217823/neo4j-cypher-inline-conversion-of-string-to-a-map + // result looks like: '{id: "g0", slug: "yoga"}' + const paramsEntries = Object.entries(params) + let mapLiteral = '' + paramsEntries.forEach((ele, index) => { + mapLiteral += index === 0 ? ' {' : '' + mapLiteral += `${ele[0]}: "${ele[1]}"` + mapLiteral += index < paramsEntries.length - 1 ? ', ' : '}' + }) + return mapLiteral +} diff --git a/backend/src/schema/resolvers/user_management.js b/backend/src/schema/resolvers/user_management.js index beb2cddb3..d88eafdae 100644 --- a/backend/src/schema/resolvers/user_management.js +++ b/backend/src/schema/resolvers/user_management.js @@ -20,16 +20,22 @@ export default { const result = await transaction.run( ` MATCH (user:User {id: $id}) - WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] as media - RETURN user {.*, socialMedia: media } as user + OPTIONAL MATCH (category:Category) WHERE NOT ((user)-[:NOT_INTERESTED_IN]->(category)) + OPTIONAL MATCH (cats:Category) + WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] AS media, category, toString(COUNT(cats)) AS categoryCount + RETURN user {.*, socialMedia: media, activeCategories: collect(category.id) } AS user, categoryCount `, { id: user.id }, ) - log(result) - return result.records.map((record) => record.get('user')) + const [categoryCount] = result.records.map((record) => record.get('categoryCount')) + const [currentUser] = result.records.map((record) => record.get('user')) + // frontend expects empty array when all categories are selected + if (currentUser.activeCategories.length === parseInt(categoryCount)) + currentUser.activeCategories = [] + return currentUser }) try { - const [currentUser] = await currentUserTransactionPromise + const currentUser = await currentUserTransactionPromise return currentUser } finally { session.close() diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js index 20e9ef9d3..50e6e84bf 100644 --- a/backend/src/schema/resolvers/user_management.spec.js +++ b/backend/src/schema/resolvers/user_management.spec.js @@ -7,6 +7,7 @@ import { createTestClient } from 'apollo-server-testing' import createServer, { context } from '../../server' import encode from '../../jwt/encode' import { getNeode } from '../../db/neo4j' +import { categories } from '../../constants/categories' const neode = getNeode() let query, mutate, variables, req, user @@ -119,6 +120,7 @@ describe('currentUser', () => { } email role + activeCategories } } ` @@ -173,6 +175,52 @@ describe('currentUser', () => { } await respondsWith(expected) }) + + describe('with categories in DB', () => { + beforeEach(async () => { + await Promise.all( + categories.map(async ({ icon, name }, index) => { + await Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + name, + icon, + }) + }), + ) + }) + + it('returns empty array for all categories', async () => { + await respondsWith({ + data: { + currentUser: expect.objectContaining({ activeCategories: [] }), + }, + }) + }) + + describe('with categories saved for current user', () => { + const saveCategorySettings = gql` + mutation ($activeCategories: [String]) { + saveCategorySettings(activeCategories: $activeCategories) + } + ` + beforeEach(async () => { + await mutate({ + mutation: saveCategorySettings, + variables: { activeCategories: ['cat1', 'cat3', 'cat5', 'cat7'] }, + }) + }) + + it('returns only the saved active categories', async () => { + const result = await query({ query: currentUserQuery, variables }) + expect(result.data.currentUser.activeCategories).toHaveLength(4) + expect(result.data.currentUser.activeCategories).toContain('cat1') + expect(result.data.currentUser.activeCategories).toContain('cat3') + expect(result.data.currentUser.activeCategories).toContain('cat5') + expect(result.data.currentUser.activeCategories).toContain('cat7') + }) + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 23a39a2a1..12f00ffb6 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -286,6 +286,10 @@ export default { { id }, ) }) + + // frontend gives [] when all categories are selected (default) + if (activeCategories.length === 0) return true + const writeTxResultPromise = session.writeTransaction(async (transaction) => { const saveCategorySettingsResponse = await transaction.run( ` diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 7116d2201..d8fce3b29 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -602,9 +602,7 @@ describe('save category settings', () => { const userQuery = gql` query ($id: ID) { User(id: $id) { - activeCategories { - id - } + activeCategories } } ` @@ -631,11 +629,7 @@ describe('save category settings', () => { data: { User: [ { - activeCategories: expect.arrayContaining([ - { id: 'cat1' }, - { id: 'cat3' }, - { id: 'cat5' }, - ]), + activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']), }, ], }, @@ -678,11 +672,11 @@ describe('save category settings', () => { User: [ { activeCategories: expect.arrayContaining([ - { id: 'cat10' }, - { id: 'cat11' }, - { id: 'cat12' }, - { id: 'cat8' }, - { id: 'cat9' }, + 'cat10', + 'cat11', + 'cat12', + 'cat8', + 'cat9', ]), }, ], diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 3d71aac78..2861b0fda 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -115,11 +115,11 @@ type User { emotions: [EMOTED] - activeCategories: [Category] @cypher( + activeCategories: [String] @cypher( statement: """ MATCH (category:Category) WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category)) - RETURN category + RETURN collect(category.id) """ ) diff --git a/webapp/assets/_new/icons/svgs/culture.svg b/webapp/assets/_new/icons/svgs/culture.svg new file mode 100644 index 000000000..d63e38cb4 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/culture.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/webapp/assets/_new/icons/svgs/energy.svg b/webapp/assets/_new/icons/svgs/energy.svg new file mode 100644 index 000000000..5035a5586 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/energy.svg @@ -0,0 +1,14 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/finance.svg b/webapp/assets/_new/icons/svgs/finance.svg new file mode 100644 index 000000000..74081bc6a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/finance.svg @@ -0,0 +1,13 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/health.svg b/webapp/assets/_new/icons/svgs/health.svg new file mode 100644 index 000000000..acf50d7c1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/health.svg @@ -0,0 +1,7 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/media.svg b/webapp/assets/_new/icons/svgs/media.svg new file mode 100644 index 000000000..d63c98610 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/media.svg @@ -0,0 +1,7 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/miscellaneous.svg b/webapp/assets/_new/icons/svgs/miscellaneous.svg new file mode 100644 index 000000000..07f8dbe3f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/miscellaneous.svg @@ -0,0 +1,13 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/mobility.svg b/webapp/assets/_new/icons/svgs/mobility.svg new file mode 100644 index 000000000..9e36ec21e --- /dev/null +++ b/webapp/assets/_new/icons/svgs/mobility.svg @@ -0,0 +1,11 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/movement.svg b/webapp/assets/_new/icons/svgs/movement.svg index ac5cd9cc0..81052875d 100644 --- a/webapp/assets/_new/icons/svgs/movement.svg +++ b/webapp/assets/_new/icons/svgs/movement.svg @@ -1,20 +1,19 @@ - - - - - - - - - - - + + + + diff --git a/webapp/assets/_new/icons/svgs/nature.svg b/webapp/assets/_new/icons/svgs/nature.svg new file mode 100644 index 000000000..d40250af4 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/nature.svg @@ -0,0 +1,18 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/networking.svg b/webapp/assets/_new/icons/svgs/networking.svg new file mode 100644 index 000000000..b8d35da69 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/networking.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/webapp/assets/_new/icons/svgs/peace.svg b/webapp/assets/_new/icons/svgs/peace.svg new file mode 100644 index 000000000..408601cae --- /dev/null +++ b/webapp/assets/_new/icons/svgs/peace.svg @@ -0,0 +1,8 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/politics.svg b/webapp/assets/_new/icons/svgs/politics.svg new file mode 100644 index 000000000..35322097d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/politics.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/webapp/assets/_new/icons/svgs/psyche.svg b/webapp/assets/_new/icons/svgs/psyche.svg new file mode 100644 index 000000000..8c285d5ca --- /dev/null +++ b/webapp/assets/_new/icons/svgs/psyche.svg @@ -0,0 +1,8 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/save.svg b/webapp/assets/_new/icons/svgs/save.svg new file mode 100644 index 000000000..31c1d8459 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/save.svg @@ -0,0 +1,5 @@ + + +save + + diff --git a/webapp/assets/_new/icons/svgs/science.svg b/webapp/assets/_new/icons/svgs/science.svg new file mode 100644 index 000000000..9d3211223 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/science.svg @@ -0,0 +1,25 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/spirituality.svg b/webapp/assets/_new/icons/svgs/spirituality.svg new file mode 100644 index 000000000..0c2757071 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/spirituality.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/webapp/components/CategoriesSelect/CategoriesSelect.vue b/webapp/components/CategoriesSelect/CategoriesSelect.vue index b7d71de2d..48852d009 100644 --- a/webapp/components/CategoriesSelect/CategoriesSelect.vue +++ b/webapp/components/CategoriesSelect/CategoriesSelect.vue @@ -17,6 +17,7 @@ diff --git a/webapp/components/Group/GroupForm.vue b/webapp/components/Group/GroupForm.vue new file mode 100644 index 000000000..d79349fda --- /dev/null +++ b/webapp/components/Group/GroupForm.vue @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + {{ formData }} + + + Reset form + + {{ update ? $t('group.update') : $t('group.save') }} + + + + + zurück + + + + + + diff --git a/webapp/components/Group/GroupLink.vue b/webapp/components/Group/GroupLink.vue new file mode 100644 index 000000000..4fcc7983d --- /dev/null +++ b/webapp/components/Group/GroupLink.vue @@ -0,0 +1,15 @@ + + + + Link zur Gruppe + + Copy Link for Invite Member please! + + + + + diff --git a/webapp/components/Group/GroupMember.vue b/webapp/components/Group/GroupMember.vue new file mode 100644 index 000000000..f522ee795 --- /dev/null +++ b/webapp/components/Group/GroupMember.vue @@ -0,0 +1,56 @@ + + + Members + + + + + + {{ scope.row.name }} loves {{ scope.row.loves }} + + + delete + + + + + diff --git a/webapp/components/Group/GroupTeaser.vue b/webapp/components/Group/GroupTeaser.vue new file mode 100644 index 000000000..33a50dca7 --- /dev/null +++ b/webapp/components/Group/GroupTeaser.vue @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/webapp/components/LoginForm/LoginForm.spec.js b/webapp/components/LoginForm/LoginForm.spec.js index 430fdbe21..10fc2c622 100644 --- a/webapp/components/LoginForm/LoginForm.spec.js +++ b/webapp/components/LoginForm/LoginForm.spec.js @@ -12,6 +12,8 @@ config.stubs['nuxt-link'] = '' config.stubs['locale-switch'] = '' config.stubs['client-only'] = '' +const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] }) + describe('LoginForm', () => { let mocks let propsData @@ -26,10 +28,15 @@ describe('LoginForm', () => { storeMocks = { getters: { 'auth/pending': () => false, + 'auth/user': authUserMock, }, actions: { 'auth/login': jest.fn(), }, + mutations: { + 'posts/TOGGLE_CATEGORY': jest.fn(), + 'posts/RESET_CATEGORIES': jest.fn(), + }, } const store = new Vuex.Store(storeMocks) mocks = { @@ -43,20 +50,46 @@ describe('LoginForm', () => { } describe('fill in email and password and submit', () => { - const fillIn = (wrapper, opts = {}) => { + const fillIn = async (wrapper, opts = {}) => { const { email = 'email@example.org', password = '1234' } = opts wrapper.find('input[name="email"]').setValue(email) wrapper.find('input[name="password"]').setValue(password) - wrapper.find('form').trigger('submit') + await wrapper.find('form').trigger('submit') } - it('dispatches login with form data', () => { - fillIn(Wrapper()) + it('dispatches login with form data', async () => { + await fillIn(Wrapper()) expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(expect.any(Object), { email: 'email@example.org', password: '1234', }) }) + + describe('setting saved categories', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('no categories saved', () => { + it('resets the categories', async () => { + await fillIn(Wrapper()) + expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toBeCalled() + expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).not.toBeCalled() + }) + }) + + describe('categories saved', () => { + it('sets the categories', async () => { + authUserMock.mockReturnValue({ activeCategories: ['cat1', 'cat9', 'cat12'] }) + await fillIn(Wrapper()) + expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toBeCalled() + expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledTimes(3) + expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat1') + expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat9') + expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat12') + }) + }) + }) }) describe('Visibility of password', () => { diff --git a/webapp/components/LoginForm/LoginForm.vue b/webapp/components/LoginForm/LoginForm.vue index 0d85ca33a..59a6d7a24 100644 --- a/webapp/components/LoginForm/LoginForm.vue +++ b/webapp/components/LoginForm/LoginForm.vue @@ -58,6 +58,7 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch' import Logo from '~/components/Logo/Logo' import ShowPassword from '../ShowPassword/ShowPassword.vue' +import { mapGetters, mapMutations } from 'vuex' export default { components: { @@ -84,12 +85,27 @@ export default { iconName() { return this.showPassword ? 'eye-slash' : 'eye' }, + ...mapGetters({ + currentUser: 'auth/user', + }), }, methods: { + ...mapMutations({ + toggleCategory: 'posts/TOGGLE_CATEGORY', + resetCategories: 'posts/RESET_CATEGORIES', + }), async onSubmit() { const { email, password } = this.form try { await this.$store.dispatch('auth/login', { email, password }) + if (this.currentUser && this.currentUser.activeCategories) { + this.resetCategories() + if (this.currentUser.activeCategories.length > 0) { + this.currentUser.activeCategories.forEach((categoryId) => { + this.toggleCategory(categoryId) + }) + } + } this.$toast.success(this.$t('login.success')) this.$emit('success') } catch (err) { diff --git a/webapp/constants/groups.js b/webapp/constants/groups.js index b4a6063f1..3abf0d12a 100644 --- a/webapp/constants/groups.js +++ b/webapp/constants/groups.js @@ -1,2 +1,4 @@ // this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js` +export const NAME_LENGTH_MIN = 3 +export const NAME_LENGTH_MAX = 50 export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags diff --git a/webapp/graphql/SaveCategories.js b/webapp/graphql/SaveCategories.js new file mode 100644 index 000000000..d6db34b8a --- /dev/null +++ b/webapp/graphql/SaveCategories.js @@ -0,0 +1,9 @@ +import gql from 'graphql-tag' + +export default () => { + return gql` + mutation ($activeCategories: [String]) { + saveCategorySettings(activeCategories: $activeCategories) + } + ` +} diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index f16323073..053cb022f 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -285,6 +285,7 @@ export const currentUserQuery = gql` id url } + activeCategories } } ` diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 7222b7c72..9c7d941f5 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -364,7 +364,11 @@ "label": "Älteste zuerst" } }, - "order-by": "Sortieren nach ..." + "order-by": "Sortieren nach ...", + "save": { + "error": "Themen konnten nicht gespeichert werden!", + "success": "Themen gespeichert!" + } }, "followButton": { "follow": "Folgen", @@ -381,6 +385,8 @@ "actionRadius": "Aktionsradius", "foundation": "Gründung", "goal": "Ziel der Gruppe", + "group-created": "Die Gruppe wurde angelegt!", + "group-updated": "Die Gruppendaten wurden geändert!", "joinLeaveButton": { "iAmMember": "Bin Mitglied", "join": "Beitreten" @@ -392,6 +398,7 @@ }, "membersCount": "Mitglieder", "membersListTitle": "Gruppenmitglieder", + "newGroup": "Erstelle eine neue Gruppe", "role": "Deine Rolle in der Gruppe", "roles": { "admin": "Administrator", @@ -399,12 +406,14 @@ "pending": "Ausstehendes Mitglied", "usual": "Einfaches Mitglied" }, + "save": "Neue Gruppe anlegen", "type": "Gruppentyp", "types": { "closed": "Geschlossene Gruppe", "hidden": "Versteckte Gruppe", "public": "Öffentliche Gruppe" - } + }, + "update": "Änderung speichern" }, "hashtags-filter": { "clearSearch": "Suche löschen", @@ -781,6 +790,7 @@ "unmute": "Stummschaltung aufheben", "unmuted": "{name} ist nicht mehr stummgeschaltet" }, + "myGroups": "Meine Gruppen", "name": "Einstellungen", "notifications": { "name": "Benachrichtigungen", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 1717eee5f..9b8796bec 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -364,7 +364,11 @@ "label": "Oldest first" } }, - "order-by": "Order by ..." + "order-by": "Order by ...", + "save": { + "error": "Failed saving topic settings!", + "success": "Topics saved!" + } }, "followButton": { "follow": "Follow", @@ -381,6 +385,8 @@ "actionRadius": "Action radius", "foundation": "Foundation", "goal": "Goal of group", + "group-created": "The group was created!", + "group-updated": "The group data has been changed.", "joinLeaveButton": { "iAmMember": "I'm a member", "join": "Join" @@ -392,6 +398,7 @@ }, "membersCount": "Members", "membersListTitle": "Group Members", + "newGroup": "Create a new Group", "role": "Your role in the group", "roles": { "admin": "Administrator", @@ -399,12 +406,14 @@ "pending": "Pending Member", "usual": "Simple Member" }, + "save": "Create new group", "type": "Group type", "types": { "closed": "Closed Group", "hidden": "Hidden Group", "public": "Public Group" - } + }, + "update": "Save change" }, "hashtags-filter": { "clearSearch": "Clear search", @@ -781,6 +790,7 @@ "unmute": "Unmute user", "unmuted": "{name} is unmuted again" }, + "myGroups": "My Groups", "name": "Settings", "notifications": { "name": "Notifications", diff --git a/webapp/pages/group/create.vue b/webapp/pages/group/create.vue new file mode 100644 index 000000000..1c95fffbc --- /dev/null +++ b/webapp/pages/group/create.vue @@ -0,0 +1,46 @@ + + + Create Groupe + + + + + + + + + + + + diff --git a/webapp/pages/group/edit/_id.vue b/webapp/pages/group/edit/_id.vue new file mode 100644 index 000000000..804905e94 --- /dev/null +++ b/webapp/pages/group/edit/_id.vue @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + zurück + + + + + diff --git a/webapp/pages/group/edit/_id/index.vue b/webapp/pages/group/edit/_id/index.vue new file mode 100644 index 000000000..7930f92fc --- /dev/null +++ b/webapp/pages/group/edit/_id/index.vue @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/webapp/pages/group/edit/_id/members.vue b/webapp/pages/group/edit/_id/members.vue new file mode 100644 index 000000000..3902137f0 --- /dev/null +++ b/webapp/pages/group/edit/_id/members.vue @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/webapp/pages/my-groups.vue b/webapp/pages/my-groups.vue new file mode 100644 index 000000000..49efa4aa1 --- /dev/null +++ b/webapp/pages/my-groups.vue @@ -0,0 +1,44 @@ + + + my groups + + + + + + + diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue index 360f1e969..660449fb6 100644 --- a/webapp/pages/settings.vue +++ b/webapp/pages/settings.vue @@ -39,6 +39,10 @@ export default { name: this.$t('settings.social-media.name'), path: `/settings/my-social-media`, }, + { + name: this.$t('settings.myGroups'), + path: `/my-groups`, + }, { name: this.$t('settings.muted-users.name'), path: `/settings/muted-users`,