diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js index 64ceb9021..16df63a48 100644 --- a/backend/src/constants/categories.js +++ b/backend/src/constants/categories.js @@ -1,3 +1,102 @@ // this file is duplicated in `backend/src/constants/metadata.js` and `webapp/constants/metadata.js` export const CATEGORIES_MIN = 1 export const CATEGORIES_MAX = 3 + +export const categories = [ + { + icon: 'users', + name: 'networking', + description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe', + }, + { + icon: 'home', + name: 'home', + description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten', + }, + { + icon: 'lightbulb', + name: 'energy', + description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...', + }, + { + icon: 'smile', + name: 'psyche', + description: 'Seele, Gefühle, Glück', + }, + { + icon: 'movement', + name: 'body-and-excercise', + description: 'Sport, Yoga, Massage, Tanzen, Entspannung', + }, + { + icon: 'balance-scale', + name: 'law', + description: 'Menschenrechte, Gesetze, Verordnungen', + }, + { + icon: 'money', + name: 'finance', + description: 'Geld, Finanzsystem, Alternativwährungen, ...', + }, + { + icon: 'child', + name: 'children', + description: 'Familie, Pädagogik, Schule, Prägung', + }, + { + icon: 'suitcase', + name: 'mobility', + description: 'Reise, Verkehr, Elektromobilität', + }, + { + icon: 'shopping-cart', + name: 'economy', + description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...', + }, + { + icon: 'angellist', + name: 'peace', + description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken', + }, + { + icon: 'university', + name: 'politics', + description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien', + }, + { + icon: 'tree', + name: 'nature', + description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt', + }, + { + icon: 'graduation-cap', + name: 'science', + description: 'Bildung, Hochschule, Publikationen, ...', + }, + { + icon: 'medkit', + name: 'health', + description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...', + }, + { + icon: 'desktop', + name: 'it-and-media', + description: + 'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps', + }, + { + icon: 'heart-o', + name: 'spirituality', + description: 'Religion, Werte, Ethik', + }, + { + icon: 'music', + name: 'culture', + description: 'Kunst, Theater, Musik, Fotografie, Film', + }, + { + icon: 'ellipsis-h', + name: 'miscellaneous', + description: '', + }, +] diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 938ebef02..57a317b47 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -1,6 +1,8 @@ import { getDriver, getNeode } from '../../db/neo4j' import { hashSync } from 'bcryptjs' import { v4 as uuid } from 'uuid' +import { categories } from '../../constants/categories' +import CONFIG from '../../config' const defaultAdmin = { email: 'admin@example.org', @@ -10,6 +12,29 @@ const defaultAdmin = { slug: 'admin', } +const createCategories = async (session) => { + const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => { + categories.forEach(({ icon, name }, index) => { + const id = `cat${index + 1}` + txc.run( + `MERGE (c:Category { + icon: "${icon}", + slug: "${name}", + name: "${name}", + id: "${id}", + createdAt: toString(datetime()) + })`, + ) + }) + }) + try { + await createCategoriesTxResultPromise + console.log('Successfully created categories!') // eslint-disable-line no-console + } catch (error) { + console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console + } +} + const createDefaultAdminUser = async (session) => { const readTxResultPromise = session.readTransaction(async (txc) => { const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount') @@ -45,7 +70,7 @@ const createDefaultAdminUser = async (session) => { }) try { await createAdminTxResultPromise - console.log('Successfully created default admin user') // eslint-disable-line no-console + console.log('Successfully created default admin user!') // eslint-disable-line no-console } catch (error) { console.log(error) // eslint-disable-line no-console } @@ -58,6 +83,7 @@ class Store { const { driver } = neode const session = driver.session() await createDefaultAdminUser(session) + if (CONFIG.CATEGORIES_ACTIVE) await createCategories(session) const writeTxResultPromise = session.writeTransaction(async (txc) => { await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices and contraints return Promise.all( diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index a71b11131..a3ba8c16b 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -5,6 +5,7 @@ import createServer from '../server' import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' +import { gql } from '../helpers/jest' import { createGroupMutation, joinGroupMutation, @@ -12,6 +13,7 @@ import { } from './graphql/groups' import { createPostMutation } from './graphql/posts' import { createCommentMutation } from './graphql/comments' +import { categories } from '../constants/categories' if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { throw new Error(`You cannot seed the database in a non-staging and real production environment!`) @@ -273,104 +275,16 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] dagobert.relateTo(louie, 'blocked'), ]) - await Promise.all([ - Factory.build('category', { - id: 'cat1', - name: 'Just For Fun', - slug: 'just-for-fun', - icon: 'smile', + await Promise.all( + categories.map(({ icon, name }, index) => { + Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + name, + icon, + }) }), - Factory.build('category', { - id: 'cat2', - name: 'Happiness & Values', - slug: 'happiness-values', - icon: 'heart-o', - }), - Factory.build('category', { - id: 'cat3', - name: 'Health & Wellbeing', - slug: 'health-wellbeing', - icon: 'medkit', - }), - Factory.build('category', { - id: 'cat4', - name: 'Environment & Nature', - slug: 'environment-nature', - icon: 'tree', - }), - Factory.build('category', { - id: 'cat5', - name: 'Animal Protection', - slug: 'animal-protection', - icon: 'paw', - }), - Factory.build('category', { - id: 'cat6', - name: 'Human Rights & Justice', - slug: 'human-rights-justice', - icon: 'balance-scale', - }), - Factory.build('category', { - id: 'cat7', - name: 'Education & Sciences', - slug: 'education-sciences', - icon: 'graduation-cap', - }), - Factory.build('category', { - id: 'cat8', - name: 'Cooperation & Development', - slug: 'cooperation-development', - icon: 'users', - }), - Factory.build('category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }), - Factory.build('category', { - id: 'cat10', - name: 'Economy & Finances', - slug: 'economy-finances', - icon: 'money', - }), - Factory.build('category', { - id: 'cat11', - name: 'Energy & Technology', - slug: 'energy-technology', - icon: 'flash', - }), - Factory.build('category', { - id: 'cat12', - name: 'IT, Internet & Data Privacy', - slug: 'it-internet-data-privacy', - icon: 'mouse-pointer', - }), - Factory.build('category', { - id: 'cat13', - name: 'Art, Culture & Sport', - slug: 'art-culture-sport', - icon: 'paint-brush', - }), - Factory.build('category', { - id: 'cat14', - name: 'Freedom of Speech', - slug: 'freedom-of-speech', - icon: 'bullhorn', - }), - Factory.build('category', { - id: 'cat15', - name: 'Consumption & Sustainability', - slug: 'consumption-sustainability', - icon: 'shopping-cart', - }), - Factory.build('category', { - id: 'cat16', - name: 'Global Peace & Nonviolence', - slug: 'global-peace-nonviolence', - icon: 'angellist', - }), - ]) + ) const [environment, nature, democracy, freedom] = await Promise.all([ Factory.build('tag', { diff --git a/backend/src/models/Category.js b/backend/src/models/Category.js index ea617adc8..9a3f47fd0 100644 --- a/backend/src/models/Category.js +++ b/backend/src/models/Category.js @@ -9,8 +9,7 @@ export default { updatedAt: { type: 'string', isoDate: true, - required: true, - default: () => new Date().toISOString(), + required: false, }, post: { type: 'relationship', diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index b09bb3edd..d9a04732c 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -358,7 +358,7 @@ export default { undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'], hasMany: { tags: '-[:TAGGED]->(related:Tag)', - // categories: '-[:CATEGORIZED]->(related:Category)', + categories: '-[:CATEGORIZED]->(related:Category)', comments: '<-[:COMMENTS]-(related:Comment)', shoutedBy: '<-[:SHOUTED]-(related:User)', emotions: '<-[related:EMOTED]', diff --git a/webapp/assets/_new/icons/svgs/child.svg b/webapp/assets/_new/icons/svgs/child.svg new file mode 100644 index 000000000..fcb5651f0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/child.svg @@ -0,0 +1,5 @@ + + +child + + diff --git a/webapp/assets/_new/icons/svgs/desktop.svg b/webapp/assets/_new/icons/svgs/desktop.svg new file mode 100644 index 000000000..ba1ef8431 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/desktop.svg @@ -0,0 +1,5 @@ + + +desktop + + diff --git a/webapp/assets/_new/icons/svgs/ellipsis-h.svg b/webapp/assets/_new/icons/svgs/ellipsis-h.svg new file mode 100644 index 000000000..eb7deeab0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/ellipsis-h.svg @@ -0,0 +1,5 @@ + + +ellipsis-h + + diff --git a/webapp/assets/_new/icons/svgs/home.svg b/webapp/assets/_new/icons/svgs/home.svg new file mode 100644 index 000000000..b1a13b06f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/home.svg @@ -0,0 +1,5 @@ + + +home + + diff --git a/webapp/assets/_new/icons/svgs/lightbulb.svg b/webapp/assets/_new/icons/svgs/lightbulb.svg new file mode 100644 index 000000000..1c19c81b1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/lightbulb.svg @@ -0,0 +1,5 @@ + + +lightbulb-o + + diff --git a/webapp/assets/_new/icons/svgs/movement.svg b/webapp/assets/_new/icons/svgs/movement.svg new file mode 100644 index 000000000..ac5cd9cc0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/movement.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/webapp/assets/_new/icons/svgs/music.svg b/webapp/assets/_new/icons/svgs/music.svg new file mode 100644 index 000000000..b84b87800 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/music.svg @@ -0,0 +1,5 @@ + + +music + + diff --git a/webapp/assets/_new/icons/svgs/suitcase.svg b/webapp/assets/_new/icons/svgs/suitcase.svg new file mode 100644 index 000000000..ceca5cbad --- /dev/null +++ b/webapp/assets/_new/icons/svgs/suitcase.svg @@ -0,0 +1,5 @@ + + +suitcase + + diff --git a/webapp/components/FilterMenu/FilterMenu.spec.js b/webapp/components/FilterMenu/FilterMenu.spec.js index 9e7ebfec0..6e9741e79 100644 --- a/webapp/components/FilterMenu/FilterMenu.spec.js +++ b/webapp/components/FilterMenu/FilterMenu.spec.js @@ -8,6 +8,9 @@ let wrapper describe('FilterMenu.vue', () => { const mocks = { $t: jest.fn((string) => string), + $env: { + CATEGORIES_ACTIVE: true, + }, } const getters = { diff --git a/webapp/components/FilterMenu/FilterMenu.vue b/webapp/components/FilterMenu/FilterMenu.vue index 9e211ccf9..84c7c1f67 100644 --- a/webapp/components/FilterMenu/FilterMenu.vue +++ b/webapp/components/FilterMenu/FilterMenu.vue @@ -14,6 +14,7 @@

{{ $t('filter-menu.filter-by') }}

+

{{ $t('filter-menu.order-by') }}

@@ -28,17 +29,24 @@ import Dropdown from '~/components/Dropdown' import { mapGetters } from 'vuex' import FollowingFilter from './FollowingFilter' import OrderByFilter from './OrderByFilter' +import CategoriesFilter from './CategoriesFilter' export default { components: { Dropdown, FollowingFilter, + CategoriesFilter, OrderByFilter, }, props: { placement: { type: String }, offset: { type: [String, Number] }, }, + data() { + return { + categoriesActive: this.$env.CATEGORIES_ACTIVE, + } + }, computed: { ...mapGetters({ filterActive: 'posts/isActive', diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 297daa511..dee8d086a 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -218,22 +218,25 @@ }, "category": { "name": { - "animal-protection": "Schutz der Tiere", - "art-culture-sport": "Kunst, Kultur & Sport", - "consumption-sustainability": "Verbrauch & Nachhaltigkeit", - "cooperation-development": "Zusammenarbeit & Entwicklung", - "democracy-politics": "Demokratie & Politik", - "economy-finances": "Wirtschaft & Finanzen", - "education-sciences": "Bildung & Wissenschaft", - "energy-technology": "Energie & Technologie", - "environment-nature": "Umwelt & Natur", - "freedom-of-speech": "Redefreiheit", - "global-peace-nonviolence": "Globaler Frieden & Gewaltlosigkeit", - "happiness-values": "Glück & Werte", - "health-wellbeing": "Gesundheit & Wohlbefinden", - "human-rights-justice": "Menschenrechte & Gerechtigkeit", - "it-internet-data-privacy": "IT, Internet & Datenschutz", - "just-for-fun": "Nur zum Spaß" + "body-and-excercise": "Körper & Bewegung", + "children": "Kinder", + "culture": "Kultur", + "economy": "Wirtschaft", + "energy": "Energie", + "finance": "Finanzen", + "health": "Gesundheit", + "home": "Wohnen", + "it-and-media": "IT & Medien", + "law": "Recht", + "miscellaneous": "Sonstiges", + "mobility": "Mobilität", + "nature": "Natur", + "networking": "Vernetzung", + "peace": "Frieden", + "politics": "Politik", + "psyche": "Psyche", + "science": "Wissenschaft", + "spirituality": "Spiritualität" } }, "emotions-label": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 8499b0290..d6172c257 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -218,22 +218,25 @@ }, "category": { "name": { - "animal-protection": "Animal Protection", - "art-culture-sport": "Art, Culture, & Sport", - "consumption-sustainability": "Consumption & Sustainability", - "cooperation-development": "Cooperation & Development", - "democracy-politics": "Democracy & Politics", - "economy-finances": "Economy & Finances", - "education-sciences": "Education & Sciences", - "energy-technology": "Energy & Technology", - "environment-nature": "Environment & Nature", - "freedom-of-speech": "Freedom of Speech", - "global-peace-nonviolence": "Global Peace & Nonviolence", - "happiness-values": "Happiness & Values", - "health-wellbeing": "Health & Wellbeing", - "human-rights-justice": "Human Rights & Justice", - "it-internet-data-privacy": "IT, Internet & Data Privacy", - "just-for-fun": "Just for Fun" + "body-and-excercise": "Body & Excercise", + "children": "Children", + "culture": "Culture", + "economy": "Economy", + "energy": "Energy", + "finance": "Finance", + "health": "Health", + "home": "Home", + "it-and-media": "IT & Media", + "law": "Law", + "miscellaneous": "Miscellaneous", + "mobility": "Mobility", + "nature": "Nature", + "networking": "Networking", + "peace": "Peace", + "politics": "Politics", + "psyche": "Psyche", + "science": "Science", + "spirituality": "Spirituality" } }, "emotions-label": {