mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch '5059-epic-groups' into post-in-group
This commit is contained in:
commit
b259295094
52
.github/workflows/publish.yml
vendored
52
.github/workflows/publish.yml
vendored
@ -3,8 +3,8 @@ name: ocelot.social publish CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# - 5093-fix-automatic-deployment # for testing while developing
|
||||
# - master
|
||||
- 5059-epic-groups # for testing while developing
|
||||
|
||||
jobs:
|
||||
##############################################################################
|
||||
@ -94,16 +94,16 @@ jobs:
|
||||
##########################################################################
|
||||
- name: Backend | Build `production` image
|
||||
run: |
|
||||
docker build --target base -t "ocelotsocialnetwork/backend:latest-base" -t "ocelotsocialnetwork/backend:${VERSION}-base" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
docker build --target code -t "ocelotsocialnetwork/backend:latest-code" -t "ocelotsocialnetwork/backend:${VERSION}-code" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
docker build --target production -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
docker build --target base -t "ocelotsocialnetwork/backend-groups:latest-base" -t "ocelotsocialnetwork/backend-groups:${VERSION}-base" -t "ocelotsocialnetwork/backend-groups:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
docker build --target code -t "ocelotsocialnetwork/backend-groups:latest-code" -t "ocelotsocialnetwork/backend-groups:${VERSION}-code" -t "ocelotsocialnetwork/backend-groups:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
docker build --target production -t "ocelotsocialnetwork/backend-groups:latest" -t "ocelotsocialnetwork/backend-groups:${VERSION}" -t "ocelotsocialnetwork/backend-groups:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
- name: Backend | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
|
||||
run: docker save "ocelotsocialnetwork/backend-groups" > /tmp/backend-groups.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-backend-production
|
||||
path: /tmp/backend.tar
|
||||
path: /tmp/backend-groups.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD PRODUCTION WEBAPP ########################################
|
||||
@ -134,16 +134,16 @@ jobs:
|
||||
##########################################################################
|
||||
- name: Webapp | Build `production` image
|
||||
run: |
|
||||
docker build --target base -t "ocelotsocialnetwork/webapp:latest-base" -t "ocelotsocialnetwork/webapp:${VERSION}-base" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
docker build --target code -t "ocelotsocialnetwork/webapp:latest-code" -t "ocelotsocialnetwork/webapp:${VERSION}-code" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
docker build --target production -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
docker build --target base -t "ocelotsocialnetwork/webapp-groups:latest-base" -t "ocelotsocialnetwork/webapp-groups:${VERSION}-base" -t "ocelotsocialnetwork/webapp-groups:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
docker build --target code -t "ocelotsocialnetwork/webapp-groups:latest-code" -t "ocelotsocialnetwork/webapp-groups:${VERSION}-code" -t "ocelotsocialnetwork/webapp-groups:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
docker build --target production -t "ocelotsocialnetwork/webapp-groups:latest" -t "ocelotsocialnetwork/webapp-groups:${VERSION}" -t "ocelotsocialnetwork/webapp-groups:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
- name: Webapp | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
|
||||
run: docker save "ocelotsocialnetwork/webapp-groups" > /tmp/webapp-groups.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-webapp-production
|
||||
path: /tmp/webapp.tar
|
||||
path: /tmp/webapp-groups.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD PRODUCTION MAINTENANCE ###################################
|
||||
@ -174,16 +174,16 @@ jobs:
|
||||
##########################################################################
|
||||
- name: Maintenance | Build `production` image
|
||||
run: |
|
||||
docker build --target base -t "ocelotsocialnetwork/maintenance:latest-base" -t "ocelotsocialnetwork/maintenance:${VERSION}-base" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target code -t "ocelotsocialnetwork/maintenance:latest-code" -t "ocelotsocialnetwork/maintenance:${VERSION}-code" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target production -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target base -t "ocelotsocialnetwork/maintenance-groups:latest-base" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}-base" -t "ocelotsocialnetwork/maintenance-groups:${BUILD_VERSION}-base" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target code -t "ocelotsocialnetwork/maintenance-groups:latest-code" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}-code" -t "ocelotsocialnetwork/maintenance-groups:${BUILD_VERSION}-code" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker build --target production -t "ocelotsocialnetwork/maintenance-groups:latest" -t "ocelotsocialnetwork/maintenance-groups:${VERSION}" -t "ocelotsocialnetwork/maintenance-groups:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
- name: Maintenance | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
|
||||
run: docker save "ocelotsocialnetwork/maintenance-groups" > /tmp/maintenance-groups.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-maintenance-production
|
||||
path: /tmp/maintenance.tar
|
||||
path: /tmp/maintenance-groups.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: UPLOAD TO DOCKERHUB ###################################################
|
||||
@ -217,21 +217,21 @@ jobs:
|
||||
name: docker-backend-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
run: docker load < /tmp/backend-groups.tar
|
||||
- name: Download Docker Image (WebApp)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docker-webapp-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
run: docker load < /tmp/webapp-groups.tar
|
||||
- name: Download Docker Image (Maintenance)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docker-maintenance-production
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/maintenance.tar
|
||||
run: docker load < /tmp/maintenance-groups.tar
|
||||
##########################################################################
|
||||
# Upload #################################################################
|
||||
##########################################################################
|
||||
@ -240,11 +240,11 @@ jobs:
|
||||
- name: Push neo4j
|
||||
run: docker push --all-tags ocelotsocialnetwork/neo4j-community
|
||||
- name: Push backend
|
||||
run: docker push --all-tags ocelotsocialnetwork/backend
|
||||
run: docker push --all-tags ocelotsocialnetwork/backend-groups
|
||||
- name: Push webapp
|
||||
run: docker push --all-tags ocelotsocialnetwork/webapp
|
||||
run: docker push --all-tags ocelotsocialnetwork/webapp-groups
|
||||
- name: Push maintenance
|
||||
run: docker push --all-tags ocelotsocialnetwork/maintenance
|
||||
run: docker push --all-tags ocelotsocialnetwork/maintenance-groups
|
||||
|
||||
##############################################################################
|
||||
# JOB: KUBERNETES DEPLOY ACTUAL/LATEST VERSION ######################################
|
||||
@ -292,11 +292,11 @@ jobs:
|
||||
# kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
- name: Deploy actual version '$BUILD_VERSION' to DigitalOcean Kubernetes
|
||||
run: |
|
||||
kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp:$BUILD_VERSION
|
||||
kubectl -n default set image deployment/ocelot-webapp container-ocelot-webapp=ocelotsocialnetwork/webapp-groups:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-webapp
|
||||
kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend:$BUILD_VERSION
|
||||
kubectl -n default set image deployment/ocelot-backend container-ocelot-backend=ocelotsocialnetwork/backend-groups:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-backend
|
||||
kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance:$BUILD_VERSION
|
||||
kubectl -n default set image deployment/ocelot-maintenance container-ocelot-maintenance=ocelotsocialnetwork/maintenance-groups:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-maintenance
|
||||
kubectl -n default set image deployment/ocelot-neo4j container-ocelot-neo4j=ocelotsocialnetwork/neo4j-community:$BUILD_VERSION
|
||||
kubectl -n default rollout restart deployment/ocelot-neo4j
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@ -4,11 +4,50 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.1.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.8...1.1.0)
|
||||
#### [1.1.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.1.0...1.1.1)
|
||||
|
||||
- chore: 🍰 Refactor Rebranding [`#5390`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5390)
|
||||
- feat: 🍰 Tooltips For Topics [`#5350`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5350)
|
||||
- feat: 🍰 Save Categories In Frontend [`#5284`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5284)
|
||||
- feat: 🍰 Add New Yunite Icons [`#5319`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5319)
|
||||
- chore: 🍰 Update Neode From v^0.4.7 To v^0.4.8 In Backend [`#5334`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5334)
|
||||
- fix: Category Filter Menu Client Only [`#5301`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5301)
|
||||
- feat: Save Category Settings [`#5261`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5261)
|
||||
- feat: Topics Menu [`#5248`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5248)
|
||||
- docs: 🍰 Document GraqhQL Playground [`#5253`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5253)
|
||||
- feat: Categories Filter Menu [`#5198`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5198)
|
||||
- fix: 🍰 Fix Test Description From `enter-nonce.vue` To `change-password` [`#5217`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5217)
|
||||
- Bump cookie-universal-nuxt from 2.1.5 to 2.2.2 in /webapp [`#5218`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5218)
|
||||
- Bump prettier from 2.2.1 to 2.7.1 in /webapp [`#5170`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5170)
|
||||
- Bump eslint-plugin-prettier from 3.1.2 to 3.4.1 in /backend [`#5211`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5211)
|
||||
- Bump slug from 4.0.2 to 6.0.0 in /backend [`#5193`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5193)
|
||||
- chore: 🍰 Fix typo in PULL_REQUEST_TEMPLATE.md file [`#5208`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5208)
|
||||
- Bump slug from 5.1.0 to 6.0.0 [`#5191`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5191)
|
||||
- Bump vue-sweetalert-icons from 4.3.0 to 4.3.1 in /webapp [`#5174`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5174)
|
||||
- feat: 🍰 Change Error Message With `Authorised` To `Authorized` All Over The Place To Have American English [`#5206`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5206)
|
||||
- Bump cross-env from 7.0.2 to 7.0.3 in /webapp [`#5168`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5168)
|
||||
- chore: 🍰 Add `--logHeapUsage` To Jest Test Call [`#5182`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5182)
|
||||
- refactor: 🍰 Rename `UserGroup` To `UserRole` [`#5143`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5143)
|
||||
- add new yunite icons [`bb0d632`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/bb0d6329e7e36ea03671318ea8dd128a6d5a5a7a)
|
||||
- cleanup refactor rebranding [`5f5c0fa`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5f5c0faa1f28cd4df7681eba335ae5998b2d9cca)
|
||||
- change color and scss in branding [`52070b8`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/52070b8c570970bf48df561134bf67cb4111b640)
|
||||
|
||||
#### [1.1.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.9...1.1.0)
|
||||
|
||||
> 4 August 2022
|
||||
|
||||
- chore: 🍰 Release v1.1.0 - Implement Categories Again [`#5145`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5145)
|
||||
- feat: Make Categories Optional [`#5102`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5102)
|
||||
- Update issue templates [`#5101`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5101)
|
||||
- chore: 🍰 Betters Automatic Deployment To `stage.ocelot.social` On Push To `master` Branch [`#5097`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5097)
|
||||
- add optional categories to teaser and post [`bc95500`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/bc955003f7c33aabe592bee782aca973b4f00cba)
|
||||
- env vatiable for CATEGORIES_ACTIVE and switch for categories in contribution form [`e31f250`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/e31f250ea5e1949f4f08e72fe82622d41ecd85f1)
|
||||
- fix some tests [`5393c2a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5393c2aeaaf070a637390c430d5f03057030ff52)
|
||||
|
||||
#### [1.0.9](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.8...1.0.9)
|
||||
|
||||
> 20 July 2022
|
||||
|
||||
- chore: 🍰 Release v1.0.9 [`#5095`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5095)
|
||||
- chore: 🍰 Automatic Deployment To `stage.ocelot.social` On Push To `master` Branch [`#5080`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5080)
|
||||
- change footer version-link [`#5091`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/5091)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
// this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js`
|
||||
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags
|
||||
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 120 // with removed HTML tags
|
||||
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags
|
||||
|
||||
@ -2,170 +2,202 @@ import gql from 'graphql-tag'
|
||||
|
||||
// ------ mutations
|
||||
|
||||
export const createGroupMutation = gql`
|
||||
mutation (
|
||||
$id: ID
|
||||
$name: String!
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String!
|
||||
$groupType: GroupType!
|
||||
$actionRadius: GroupActionRadius!
|
||||
$categoryIds: [ID]
|
||||
$locationName: String
|
||||
) {
|
||||
CreateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
groupType: $groupType
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
locationName: $locationName
|
||||
export const createGroupMutation = () => {
|
||||
return gql`
|
||||
mutation (
|
||||
$id: ID
|
||||
$name: String!
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String!
|
||||
$groupType: GroupType!
|
||||
$actionRadius: GroupActionRadius!
|
||||
$categoryIds: [ID]
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
CreateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
groupType: $groupType
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
locationName
|
||||
location {
|
||||
name
|
||||
nameDE
|
||||
nameEN
|
||||
}
|
||||
myRole
|
||||
}
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const updateGroupMutation = gql`
|
||||
mutation (
|
||||
$id: ID!
|
||||
$name: String
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String
|
||||
$actionRadius: GroupActionRadius
|
||||
$categoryIds: [ID]
|
||||
$avatar: ImageInput
|
||||
$locationName: String
|
||||
) {
|
||||
UpdateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
export const updateGroupMutation = () => {
|
||||
return gql`
|
||||
mutation (
|
||||
$id: ID!
|
||||
$name: String
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String
|
||||
$actionRadius: GroupActionRadius
|
||||
$categoryIds: [ID]
|
||||
$avatar: ImageInput
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
UpdateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
# avatar # test this as result
|
||||
locationName
|
||||
location {
|
||||
name
|
||||
nameDE
|
||||
nameEN
|
||||
}
|
||||
myRole
|
||||
}
|
||||
# avatar # test this as result
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const joinGroupMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
JoinGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const joinGroupMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
JoinGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const leaveGroupMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
LeaveGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const leaveGroupMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
LeaveGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const changeGroupMemberRoleMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
|
||||
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const changeGroupMemberRoleMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
|
||||
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
// ------ queries
|
||||
|
||||
export const groupQuery = gql`
|
||||
query ($isMember: Boolean, $id: ID, $slug: String) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
export const groupQuery = () => {
|
||||
return gql`
|
||||
query ($isMember: Boolean, $id: ID, $slug: String) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
locationName
|
||||
location {
|
||||
name
|
||||
nameDE
|
||||
nameEN
|
||||
}
|
||||
myRole
|
||||
}
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const groupMembersQuery = gql`
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const groupMembersQuery = () => {
|
||||
return gql`
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
@ -305,7 +305,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
authenticatedUser = await peterLustig.toJson()
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g0',
|
||||
name: 'Investigative Journalism',
|
||||
@ -313,27 +313,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
description: `<p class=""><em>English:</em></p><p class="">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class="">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class="">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class="">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>`,
|
||||
groupType: 'hidden',
|
||||
actionRadius: 'global',
|
||||
categoryIds: ['cat6', 'cat9', 'cat14'],
|
||||
categoryIds: ['cat6', 'cat12', 'cat16'],
|
||||
locationName: 'Hamburg, Germany',
|
||||
},
|
||||
}),
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g0',
|
||||
userId: 'u2',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g0',
|
||||
userId: 'u4',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g0',
|
||||
userId: 'u6',
|
||||
@ -342,7 +343,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g0',
|
||||
userId: 'u2',
|
||||
@ -350,7 +351,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g0',
|
||||
userId: 'u4',
|
||||
@ -362,7 +363,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
authenticatedUser = await jennyRostock.toJson()
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g1',
|
||||
name: 'School For Citizens',
|
||||
@ -370,41 +371,42 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
description: `<p class=""><em>English</em></p><h3>Our goal</h3><p>Only those who enjoy learning and do not lose their curiosity can obtain a good education for life and continue to learn with joy throughout their lives.</p><h3>Curiosity</h3><p>For this we need a school that takes up the curiosity of the children, the people, and satisfies it through a lot of experience.</p><p><br></p><p><em>Deutsch</em></p><h3>Unser Ziel</h3><p class="">Nur wer Spaß am Lernen hat und seine Neugier nicht verliert, kann gute Bildung für's Leben erlangen und sein ganzes Leben mit Freude weiter lernen.</p><h3>Neugier</h3><p class="">Dazu benötigen wir eine Schule, die die Neugier der Kinder, der Menschen, aufnimmt und durch viel Erfahrung befriedigt.</p>`,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
categoryIds: ['cat7', 'cat9', 'cat16'],
|
||||
categoryIds: ['cat8', 'cat14'],
|
||||
locationName: 'France',
|
||||
},
|
||||
}),
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u1',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u2',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u5',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u6',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u7',
|
||||
@ -413,7 +415,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u1',
|
||||
@ -421,7 +423,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u5',
|
||||
@ -429,7 +431,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g1',
|
||||
userId: 'u6',
|
||||
@ -441,7 +443,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
authenticatedUser = await bobDerBaumeister.toJson()
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
id: 'g2',
|
||||
name: 'Yoga Practice',
|
||||
@ -449,41 +451,41 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
description: `<h3>What Is yoga?</h3><p>Yoga is not just about practicing asanas. It's about how we do it.</p><p class="">And practicing asanas doesn't have to be yoga, it can be more athletic than yogic.</p><h3>What makes practicing asanas yogic?</h3><p class="">The important thing is:</p><ul><li><p>Use the exercises (consciously) for your personal development.</p></li></ul>`,
|
||||
groupType: 'public',
|
||||
actionRadius: 'interplanetary',
|
||||
categoryIds: ['cat3', 'cat13', 'cat16'],
|
||||
categoryIds: ['cat4', 'cat5', 'cat17'],
|
||||
},
|
||||
}),
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u3',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u4',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u5',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u6',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: joinGroupMutation,
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u7',
|
||||
@ -492,31 +494,31 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
])
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u3',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u4',
|
||||
roleInGroup: 'pending',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u4',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u5',
|
||||
roleInGroup: 'usual',
|
||||
roleInGroup: 'admin',
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
groupId: 'g2',
|
||||
userId: 'u6',
|
||||
|
||||
@ -82,7 +82,7 @@ const isAllowedToChangeGroupSettings = rule({
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedSeeingMembersOfGroup = rule({
|
||||
const isAllowedSeeingGroupMembers = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
if (!(user && user.id)) return false
|
||||
@ -312,7 +312,7 @@ export default shield(
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Group: isAuthenticated,
|
||||
GroupMembers: isAllowedSeeingMembersOfGroup,
|
||||
GroupMembers: isAllowedSeeingGroupMembers,
|
||||
Post: allow,
|
||||
profilePagePosts: allow,
|
||||
Comment: allow,
|
||||
|
||||
@ -51,6 +51,7 @@ beforeEach(async () => {
|
||||
await Factory.build('category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
slug: 'democracy-politics',
|
||||
icon: 'university',
|
||||
})
|
||||
authenticatedUser = await admin.toJson()
|
||||
@ -79,7 +80,7 @@ describe('slugifyMiddleware', () => {
|
||||
it('generates a slug based on name', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -93,13 +94,14 @@ describe('slugifyMiddleware', () => {
|
||||
actionRadius: 'national',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('generates a slug based on given slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
...variables,
|
||||
slug: 'the-group',
|
||||
@ -111,6 +113,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'the-group',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -118,7 +121,7 @@ describe('slugifyMiddleware', () => {
|
||||
describe('if slug exists', () => {
|
||||
beforeEach(async () => {
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
@ -131,7 +134,7 @@ describe('slugifyMiddleware', () => {
|
||||
it('chooses another slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
@ -144,6 +147,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'pre-existing-group-1',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
@ -152,7 +156,7 @@ describe('slugifyMiddleware', () => {
|
||||
try {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
...variables,
|
||||
name: 'Pre-Existing Group',
|
||||
@ -194,7 +198,7 @@ describe('slugifyMiddleware', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
createGroupResult = await mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
name: 'The Best Group',
|
||||
slug: 'the-best-group',
|
||||
@ -213,7 +217,7 @@ describe('slugifyMiddleware', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'My Best Group',
|
||||
@ -231,6 +235,7 @@ describe('slugifyMiddleware', () => {
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -239,7 +244,7 @@ describe('slugifyMiddleware', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
slug: 'my-best-group',
|
||||
@ -257,6 +262,7 @@ describe('slugifyMiddleware', () => {
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -265,7 +271,7 @@ describe('slugifyMiddleware', () => {
|
||||
describe('if new slug exists in another group', () => {
|
||||
beforeEach(async () => {
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables: {
|
||||
name: 'Pre-Existing Group',
|
||||
slug: 'pre-existing-group',
|
||||
@ -282,7 +288,7 @@ describe('slugifyMiddleware', () => {
|
||||
it('has unique slug "*-1"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'Pre-Existing Group',
|
||||
@ -300,6 +306,7 @@ describe('slugifyMiddleware', () => {
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -309,7 +316,7 @@ describe('slugifyMiddleware', () => {
|
||||
try {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation,
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
slug: 'pre-existing-group',
|
||||
@ -368,6 +375,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'i-am-a-brand-new-post',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
@ -386,6 +394,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'the-post',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -422,6 +431,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'pre-existing-post-1',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
@ -504,6 +514,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'i-am-a-user',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
@ -522,6 +533,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'the-user',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -546,6 +558,7 @@ describe('slugifyMiddleware', () => {
|
||||
slug: 'i-am-a-user-1',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ export default makeAugmentedSchema({
|
||||
'FILED',
|
||||
'REVIEWED',
|
||||
'Report',
|
||||
'Group',
|
||||
],
|
||||
},
|
||||
mutation: false,
|
||||
|
||||
@ -9,6 +9,7 @@ import Resolver, {
|
||||
convertObjectToCypherMapLiteral,
|
||||
} from './helpers/Resolver'
|
||||
import { mergeImage } from './images/images'
|
||||
import { createOrUpdateLocations } from './users/location'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
@ -85,6 +86,7 @@ export default {
|
||||
CreateGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
delete params.categoryIds
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) {
|
||||
throw new UserInputError('Too view categories!')
|
||||
}
|
||||
@ -135,7 +137,10 @@ export default {
|
||||
return group
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
const group = await writeTxResultPromise
|
||||
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('Group with this slug already exists!')
|
||||
@ -146,9 +151,11 @@ export default {
|
||||
},
|
||||
UpdateGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { categoryIds } = params
|
||||
const { id: groupId, avatar: avatarInput } = params
|
||||
delete params.categoryIds
|
||||
const { id: groupId, avatar: avatarInput } = params
|
||||
delete params.avatar
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds) {
|
||||
if (categoryIds.length < CATEGORIES_MIN) {
|
||||
throw new UserInputError('Too view categories!')
|
||||
@ -206,7 +213,10 @@ export default {
|
||||
return group
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
const group = await writeTxResultPromise
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('Group with this slug already exists!')
|
||||
@ -300,12 +310,14 @@ export default {
|
||||
},
|
||||
Group: {
|
||||
...Resolver('Group', {
|
||||
undefinedToNull: ['deleted', 'disabled', 'locationName', 'about'],
|
||||
hasMany: {
|
||||
categories: '-[:CATEGORIZED]->(related:Category)',
|
||||
posts: '<-[:IN]-(related:Post)',
|
||||
},
|
||||
hasOne: {
|
||||
avatar: '-[:AVATAR_IMAGE]->(related:Image)',
|
||||
location: '-[:IS_IN]->(related:Location)',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||
import { mergeImage, deleteImage } from './images/images'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import log from './helpers/databaseLogger'
|
||||
import createOrUpdateLocations from './users/location'
|
||||
import { createOrUpdateLocations } from './users/location'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -139,9 +139,10 @@ export default {
|
||||
return blockedUser.toJson()
|
||||
},
|
||||
UpdateUser: async (_parent, params, context, _resolveInfo) => {
|
||||
const { termsAndConditionsAgreedVersion } = params
|
||||
const { avatar: avatarInput } = params
|
||||
delete params.avatar
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
const { termsAndConditionsAgreedVersion } = params
|
||||
if (termsAndConditionsAgreedVersion) {
|
||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||
@ -169,7 +170,8 @@ export default {
|
||||
})
|
||||
try {
|
||||
const user = await writeTxResultPromise
|
||||
await createOrUpdateLocations(params.id, params.locationName, session)
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
|
||||
await createOrUpdateLocations('User', params.id, params.locationName, session)
|
||||
return user
|
||||
} catch (error) {
|
||||
throw new UserInputError(error.message)
|
||||
|
||||
@ -161,7 +161,7 @@ describe('UpdateUser', () => {
|
||||
$id: ID!
|
||||
$name: String
|
||||
$termsAndConditionsAgreedVersion: String
|
||||
$locationName: String
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
UpdateUser(
|
||||
id: $id
|
||||
@ -174,6 +174,11 @@ describe('UpdateUser', () => {
|
||||
termsAndConditionsAgreedVersion
|
||||
termsAndConditionsAgreedAt
|
||||
locationName
|
||||
location {
|
||||
name
|
||||
nameDE
|
||||
nameEN
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -289,11 +294,39 @@ describe('UpdateUser', () => {
|
||||
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
|
||||
})
|
||||
|
||||
it('supports updating location', async () => {
|
||||
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' }
|
||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
|
||||
data: { UpdateUser: { locationName: 'Hamburg, New Jersey, United States' } },
|
||||
errors: undefined,
|
||||
describe('supports updating location', () => {
|
||||
describe('change location to "Hamburg, New Jersey, United States"', () => {
|
||||
it('has updated location to "Hamburg, New Jersey, United States"', async () => {
|
||||
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' }
|
||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateUser: {
|
||||
locationName: 'Hamburg, New Jersey, United States',
|
||||
location: expect.objectContaining({
|
||||
name: 'Hamburg',
|
||||
nameDE: 'Hamburg',
|
||||
nameEN: 'Hamburg',
|
||||
}),
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('change location to unset location', () => {
|
||||
it('has updated location to unset location', async () => {
|
||||
variables = { ...variables, locationName: '' }
|
||||
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateUser: {
|
||||
locationName: null,
|
||||
location: null,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import request from 'request'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Debug from 'debug'
|
||||
import asyncForEach from '../../../helpers/asyncForEach'
|
||||
import CONFIG from '../../../config'
|
||||
@ -62,77 +61,86 @@ const createLocation = async (session, mapboxData) => {
|
||||
})
|
||||
}
|
||||
|
||||
const createOrUpdateLocations = async (userId, locationName, session) => {
|
||||
if (isEmpty(locationName)) {
|
||||
return
|
||||
}
|
||||
const res = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||
locationName,
|
||||
)}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${locales.join(
|
||||
',',
|
||||
)}`,
|
||||
)
|
||||
export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, session) => {
|
||||
if (locationName === undefined) return
|
||||
|
||||
debug(res)
|
||||
let locationId
|
||||
|
||||
if (!res || !res.features || !res.features[0]) {
|
||||
throw new UserInputError('locationName is invalid')
|
||||
}
|
||||
if (locationName !== null) {
|
||||
const res = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||
locationName,
|
||||
)}.json?access_token=${
|
||||
CONFIG.MAPBOX_TOKEN
|
||||
}&types=region,place,country&language=${locales.join(',')}`,
|
||||
)
|
||||
|
||||
let data
|
||||
debug(res)
|
||||
|
||||
res.features.forEach((item) => {
|
||||
if (item.matching_place_name === locationName) {
|
||||
data = item
|
||||
if (!res || !res.features || !res.features[0]) {
|
||||
throw new UserInputError('locationName is invalid')
|
||||
}
|
||||
})
|
||||
if (!data) {
|
||||
data = res.features[0]
|
||||
}
|
||||
|
||||
if (!data || !data.place_type || !data.place_type.length) {
|
||||
throw new UserInputError('locationName is invalid')
|
||||
}
|
||||
let data
|
||||
|
||||
if (data.place_type.length > 1) {
|
||||
data.id = 'region.' + data.id.split('.')[1]
|
||||
}
|
||||
await createLocation(session, data)
|
||||
|
||||
let parent = data
|
||||
|
||||
if (data.context) {
|
||||
await asyncForEach(data.context, async (ctx) => {
|
||||
await createLocation(session, ctx)
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
|
||||
MERGE (child)<-[:IS_IN]-(parent)
|
||||
RETURN child.id, parent.id
|
||||
`,
|
||||
{
|
||||
parentId: parent.id,
|
||||
childId: ctx.id,
|
||||
},
|
||||
)
|
||||
})
|
||||
parent = ctx
|
||||
res.features.forEach((item) => {
|
||||
if (item.matching_place_name === locationName) {
|
||||
data = item
|
||||
}
|
||||
})
|
||||
if (!data) {
|
||||
data = res.features[0]
|
||||
}
|
||||
|
||||
if (!data || !data.place_type || !data.place_type.length) {
|
||||
throw new UserInputError('locationName is invalid')
|
||||
}
|
||||
|
||||
if (data.place_type.length > 1) {
|
||||
data.id = 'region.' + data.id.split('.')[1]
|
||||
}
|
||||
await createLocation(session, data)
|
||||
|
||||
let parent = data
|
||||
|
||||
if (data.context) {
|
||||
await asyncForEach(data.context, async (ctx) => {
|
||||
await createLocation(session, ctx)
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (parent:Location {id: $parentId}), (child:Location {id: $childId})
|
||||
MERGE (child)<-[:IS_IN]-(parent)
|
||||
RETURN child.id, parent.id
|
||||
`,
|
||||
{
|
||||
parentId: parent.id,
|
||||
childId: ctx.id,
|
||||
},
|
||||
)
|
||||
})
|
||||
parent = ctx
|
||||
})
|
||||
}
|
||||
|
||||
locationId = data.id
|
||||
} else {
|
||||
locationId = 'non-existent-id'
|
||||
}
|
||||
// delete all current locations from user and add new location
|
||||
|
||||
// delete all current locations from node and add new location
|
||||
await session.writeTransaction((transaction) => {
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})-[relationship:IS_IN]->(location:Location)
|
||||
DETACH DELETE relationship
|
||||
WITH user
|
||||
MATCH (location:Location {id: $locationId})
|
||||
MERGE (user)-[:IS_IN]->(location)
|
||||
RETURN location.id, user.id
|
||||
`,
|
||||
{ userId: userId, locationId: data.id },
|
||||
MATCH (node:${nodeLabel} {id: $nodeId})
|
||||
OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location)
|
||||
DELETE relationship
|
||||
WITH node
|
||||
MATCH (location:Location {id: $locationId})
|
||||
MERGE (node)-[:IS_IN]->(location)
|
||||
RETURN location.id, node.id
|
||||
`,
|
||||
{ nodeId, locationId },
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -147,5 +155,3 @@ export const queryLocations = async ({ place, lang }) => {
|
||||
}
|
||||
return res.features
|
||||
}
|
||||
|
||||
export default createOrUpdateLocations
|
||||
|
||||
@ -121,7 +121,7 @@ describe('Location Service', () => {
|
||||
const result = await query({ query: queryLocations, variables })
|
||||
expect(result.data.queryLocations).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ id: 'place.14094307404564380', place_name: 'Berlin, Germany' },
|
||||
{ id: expect.stringMatching(/^place\.[0-9]+$/), place_name: 'Berlin, Germany' },
|
||||
{
|
||||
id: expect.stringMatching(/^place\.[0-9]+$/),
|
||||
place_name: 'Berlin, Maryland, United States',
|
||||
|
||||
@ -29,11 +29,12 @@ type Group {
|
||||
|
||||
about: String # goal
|
||||
description: String!
|
||||
descriptionExcerpt: String!
|
||||
groupType: GroupType!
|
||||
actionRadius: GroupActionRadius!
|
||||
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
locationName: String
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
|
||||
categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
@ -104,7 +105,7 @@ type Mutation {
|
||||
actionRadius: GroupActionRadius!
|
||||
categoryIds: [ID]
|
||||
# avatar: ImageInput # a group can not be created with an avatar
|
||||
locationName: String # test this as result
|
||||
locationName: String # empty string '' sets it to null
|
||||
): Group
|
||||
|
||||
UpdateGroup(
|
||||
@ -117,7 +118,7 @@ type Mutation {
|
||||
actionRadius: GroupActionRadius
|
||||
categoryIds: [ID]
|
||||
avatar: ImageInput # test this as result
|
||||
locationName: String # test this as result
|
||||
locationName: String # empty string '' sets it to null
|
||||
): Group
|
||||
|
||||
# DeleteGroup(id: ID!): Group
|
||||
|
||||
@ -33,8 +33,8 @@ type User {
|
||||
invitedBy: User @relation(name: "INVITED", direction: "IN")
|
||||
invited: [User] @relation(name: "INVITED", direction: "OUT")
|
||||
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
locationName: String
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN")
|
||||
|
||||
@ -212,7 +212,7 @@ type Mutation {
|
||||
email: String
|
||||
slug: String
|
||||
avatar: ImageInput
|
||||
locationName: String
|
||||
locationName: String # empty string '' sets it to null
|
||||
about: String
|
||||
termsAndConditionsAgreedVersion: String
|
||||
termsAndConditionsAgreedAt: String
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "Free and open source software program code available to run social networks.",
|
||||
"author": "ocelot.social Community",
|
||||
"license": "MIT",
|
||||
|
||||
@ -268,6 +268,7 @@ $size-avatar-large: 114px;
|
||||
* @presenter Spacing
|
||||
*/
|
||||
|
||||
$size-button-large: 50px;
|
||||
$size-button-base: 36px;
|
||||
$size-button-small: 26px;
|
||||
|
||||
@ -356,3 +357,22 @@ $media-query-x-large: (min-width: 1200px);
|
||||
/**
|
||||
* @tokens Background Images
|
||||
*/
|
||||
|
||||
/**
|
||||
* @tokens Header Color
|
||||
*/
|
||||
|
||||
$color-header-background: $color-neutral-100;
|
||||
|
||||
/**
|
||||
* @tokens Footer Color
|
||||
*/
|
||||
|
||||
$color-footer-background: $color-neutral-100;
|
||||
$color-footer-link: $color-primary;
|
||||
|
||||
/**
|
||||
* @tokens Locale Menu Color
|
||||
*/
|
||||
|
||||
$color-locale-menu: $text-color-soft;
|
||||
5
webapp/assets/styles/imports/_branding.scss
Normal file
5
webapp/assets/styles/imports/_branding.scss
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
*
|
||||
* Here, all SCSS variables and classes can be adapted to your custom design.
|
||||
*
|
||||
*/
|
||||
@ -179,4 +179,4 @@ hr {
|
||||
|
||||
.dropdown-arrow {
|
||||
font-size: $font-size-xx-small;
|
||||
}
|
||||
}
|
||||
@ -90,6 +90,13 @@ describe('AvatarMenu.vue', () => {
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to "My groups"', () => {
|
||||
const profileLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/my-groups'))
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to the notifications page', () => {
|
||||
const notificationsLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
@ -103,6 +110,11 @@ describe('AvatarMenu.vue', () => {
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/settings'))
|
||||
expect(settingsLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 4 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
expect(allLinks).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
|
||||
describe('role moderator', () => {
|
||||
@ -125,9 +137,9 @@ describe('AvatarMenu.vue', () => {
|
||||
expect(moderationLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 4 links', () => {
|
||||
it('displays a total of 5 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
expect(allLinks).toHaveLength(4)
|
||||
expect(allLinks).toHaveLength(5)
|
||||
})
|
||||
})
|
||||
|
||||
@ -151,9 +163,9 @@ describe('AvatarMenu.vue', () => {
|
||||
expect(adminLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 5 links', () => {
|
||||
it('displays a total of 6 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
expect(allLinks).toHaveLength(5)
|
||||
expect(allLinks).toHaveLength(6)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -72,10 +72,15 @@ export default {
|
||||
}
|
||||
const routes = [
|
||||
{
|
||||
name: this.$t('profile.name'),
|
||||
name: this.$t('header.avatarMenu.myProfile'),
|
||||
path: `/profile/${this.user.id}/${this.user.slug}`,
|
||||
icon: 'user',
|
||||
},
|
||||
{
|
||||
name: this.$t('header.avatarMenu.myGroups'),
|
||||
path: '/my-groups',
|
||||
icon: 'users',
|
||||
},
|
||||
{
|
||||
name: this.$t('notifications.pageLink'),
|
||||
path: '/notifications',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<base-button
|
||||
class="track-button"
|
||||
class="join-leave-button"
|
||||
:disabled="disabled"
|
||||
:loading="localLoading"
|
||||
:icon="icon"
|
||||
@ -108,7 +108,7 @@ export default {
|
||||
},
|
||||
async joinLeave() {
|
||||
const join = !this.isMember
|
||||
const mutation = join ? joinGroupMutation : leaveGroupMutation
|
||||
const mutation = join ? joinGroupMutation() : leaveGroupMutation()
|
||||
|
||||
this.hovered = false
|
||||
this.$emit('prepare', join)
|
||||
@ -129,7 +129,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.track-button {
|
||||
.join-leave-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
:disabled="isDisabled(category.id)"
|
||||
:icon="category.icon"
|
||||
size="small"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
>
|
||||
{{ $t(`contribution.category.name.${category.slug}`) }}
|
||||
</base-button>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
:offset="offset"
|
||||
>
|
||||
<slot :toggleMenu="toggleMenu" :openMenu="openMenu" :closeMenu="closeMenu" :isOpen="isOpen" />
|
||||
<div slot="popover" @mouseover="popoverMouseEnter" @mouseleave="popoveMouseLeave">
|
||||
<div slot="popover" @mouseover="popoverMouseEnter" @mouseleave="popoverMouseLeave">
|
||||
<slot
|
||||
name="popover"
|
||||
:toggleMenu="toggleMenu"
|
||||
@ -97,7 +97,7 @@ export default {
|
||||
}
|
||||
this.clearTimeouts()
|
||||
if (useTimeout === true) {
|
||||
this.popoveMouseLeave()
|
||||
this.popoverMouseLeave()
|
||||
} else {
|
||||
this.isPopoverOpen = false
|
||||
}
|
||||
@ -113,7 +113,7 @@ export default {
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
popoveMouseLeave() {
|
||||
popoverMouseLeave() {
|
||||
if (this.developerNoAutoClosing) return
|
||||
if (this.disabled) {
|
||||
return
|
||||
|
||||
@ -17,6 +17,10 @@
|
||||
:filled="filteredCategoryIds.includes(category.id)"
|
||||
:label="$t(`contribution.category.name.${category.slug}`)"
|
||||
@click="toggleCategory(category.id)"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
<template>
|
||||
<dropdown ref="category-menu" placement="top-start" :offset="8" class="category-menu">
|
||||
<base-button
|
||||
slot="default"
|
||||
:filled="filterActive"
|
||||
:ghost="!filterActive"
|
||||
slot-scope="{ toggleMenu }"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<ds-text uppercase>{{ $t('admin.categories.name') }}</ds-text>
|
||||
</base-button>
|
||||
<a href="#" slot="default" slot-scope="{ toggleMenu }" @click.prevent="toggleMenu()">
|
||||
<ds-text bold size="large">{{ $t('admin.categories.name') }}</ds-text>
|
||||
</a>
|
||||
<template slot="popover">
|
||||
<div class="category-menu-options">
|
||||
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
|
||||
@ -40,6 +34,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
// TODO: implement visibility of active filter later on
|
||||
filterActive: 'posts/isActive',
|
||||
}),
|
||||
},
|
||||
|
||||
5
webapp/components/Group/GroupButton.vue
Normal file
5
webapp/components/Group/GroupButton.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<nuxt-link to="/my-groups"><base-button icon="users" circle ghost /></nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-container class="group-card">
|
||||
<ds-page-title heading="Groups"></ds-page-title>
|
||||
<ds-card v-for="item in items" :key="item.id" space="xx-small">
|
||||
<nuxt-link :to="`/group/${item.id}`">{{ item.name }}</nuxt-link>
|
||||
{{ item.categories ? item.categories.map((category) => category.name) : [] }}
|
||||
<div>{{ item }}</div>
|
||||
<ds-space>
|
||||
<base-button
|
||||
v-if="item.myRole === 'owner'"
|
||||
icon="trash"
|
||||
@click="deleteGroup(item)"
|
||||
></base-button>
|
||||
<base-button
|
||||
v-if="item.myRole === 'pending'"
|
||||
icon="question-circle"
|
||||
@click="removePending(item)"
|
||||
></base-button>
|
||||
<base-button
|
||||
v-if="item.myRole === 'owner'"
|
||||
icon="edit"
|
||||
@click="editGroup(item)"
|
||||
></base-button>
|
||||
<base-button
|
||||
v-if="item.myRole === 'usual'"
|
||||
icon="close"
|
||||
@click="unfollowGroup(item)"
|
||||
></base-button>
|
||||
<base-button
|
||||
v-if="item.myRole === null"
|
||||
icon="plus"
|
||||
@click="addMemeberToGroup(item)"
|
||||
></base-button>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'GroupList',
|
||||
props: {
|
||||
items: { type: Array, default: () => [] },
|
||||
},
|
||||
methods: {
|
||||
removePending() {
|
||||
alert('removePending group')
|
||||
},
|
||||
editGroup(item) {
|
||||
this.$router.push({ path: `/group/edit/${item.id}` })
|
||||
},
|
||||
deleteGroup() {
|
||||
alert('delete group')
|
||||
},
|
||||
unfollowGroup() {
|
||||
alert('unfollow group')
|
||||
},
|
||||
addMemeberToGroup() {
|
||||
alert('addMemeberToGroup group')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
35
webapp/components/Group/GroupContentMenu.spec.js
Normal file
35
webapp/components/Group/GroupContentMenu.spec.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import GroupContentMenu from './GroupContentMenu.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
resource: {},
|
||||
group: {},
|
||||
resourceType: 'group',
|
||||
}
|
||||
|
||||
describe('GroupContentMenu', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(GroupContentMenu, { propsData, mocks, localVue })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.findAll('.group-menu')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
99
webapp/components/Group/GroupContentMenu.vue
Normal file
99
webapp/components/Group/GroupContentMenu.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<dropdown class="group-menu" :placement="placement" offset="5">
|
||||
<template #default="{ toggleMenu }">
|
||||
<slot name="button" :toggleMenu="toggleMenu">
|
||||
<base-button
|
||||
icon="ellipsis-v"
|
||||
size="small"
|
||||
circle
|
||||
@click.prevent="toggleMenu()"
|
||||
data-test="group-menu-button"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<template #popover="{ toggleMenu }">
|
||||
<div class="group-menu-popover">
|
||||
<ds-menu :routes="routes">
|
||||
<template #menuitem="item">
|
||||
{{ item.parents }}
|
||||
<ds-menu-item
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||
>
|
||||
<base-icon :name="item.route.icon" />
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
</template>
|
||||
</ds-menu>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
|
||||
export default {
|
||||
name: 'GroupContentMenu',
|
||||
components: {
|
||||
Dropdown,
|
||||
},
|
||||
props: {
|
||||
placement: { type: String, default: 'bottom-end' },
|
||||
resource: { type: Object, required: true },
|
||||
group: { type: Object, required: true },
|
||||
resourceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => {
|
||||
return value.match(/(group)/)
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
const routes = []
|
||||
|
||||
if (this.resourceType === 'group') {
|
||||
routes.push({
|
||||
label: this.$t('group.contentMenu.visitGroupPage'),
|
||||
icon: 'home',
|
||||
name: 'group-id-slug',
|
||||
params: { id: this.group.id, slug: this.group.slug },
|
||||
})
|
||||
if (this.group.myRole === 'owner') {
|
||||
routes.push({
|
||||
label: this.$t('admin.settings.name'),
|
||||
path: `/group/edit/${this.resource.id}`,
|
||||
icon: 'edit',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return routes
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openItem(route, toggleMenu) {
|
||||
if (route.callback) {
|
||||
route.callback()
|
||||
} else {
|
||||
this.$router.push(route)
|
||||
}
|
||||
toggleMenu()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-menu-popover {
|
||||
nav {
|
||||
margin-top: -$space-xx-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
margin-left: -$space-x-small;
|
||||
margin-right: -$space-x-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
webapp/components/Group/GroupForm.spec.js
Normal file
39
webapp/components/Group/GroupForm.spec.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import GroupForm from './GroupForm.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
const propsData = {
|
||||
update: false,
|
||||
group: {},
|
||||
}
|
||||
|
||||
describe('GroupForm', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$env: {
|
||||
CATEGORIES_ACTIVE: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(GroupForm, { propsData, mocks, localVue })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.findAll('.group-form')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,68 +1,132 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-container>
|
||||
<ds-form
|
||||
class="group-form"
|
||||
ref="groupForm"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="submit"
|
||||
<ds-form
|
||||
class="group-form"
|
||||
ref="groupForm"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="submit"
|
||||
>
|
||||
<ds-input
|
||||
:label="$t('group.name')"
|
||||
v-model="formData.name"
|
||||
:placeholder="`${$t('group.name')} …`"
|
||||
></ds-input>
|
||||
<ds-input
|
||||
v-if="update"
|
||||
:label="$t('group.labelSlug')"
|
||||
v-model="formData.slug"
|
||||
icon="at"
|
||||
:placeholder="`${$t('group.labelSlug')} …`"
|
||||
></ds-input>
|
||||
<!-- groupType -->
|
||||
<!-- TODO: change it has to be implemented later -->
|
||||
<!-- TODO: move 'ds-select' from styleguide to main code and implement missen translation etc. functionality -->
|
||||
<!-- <ds-select
|
||||
id="groupType"
|
||||
:label="$t('group.type')"
|
||||
:v-model="formData.groupType"
|
||||
:label-prop="'label'"
|
||||
:options="groupTypeOptions"
|
||||
icon="user"
|
||||
:placeholder="$t('group.type') + ' …'"
|
||||
></ds-select> -->
|
||||
<ds-text class="select-label">
|
||||
{{ $t('group.type') }}
|
||||
</ds-text>
|
||||
<ds-icon class="select-icon" name="user" />
|
||||
<select
|
||||
class="select"
|
||||
:options="groupTypeOptions"
|
||||
:value="formData.groupType"
|
||||
:disabled="update"
|
||||
@change="changeGroupType($event)"
|
||||
>
|
||||
<ds-input
|
||||
v-model="formData.name"
|
||||
label="Gruppenname"
|
||||
placeholder="Your name ..."
|
||||
></ds-input>
|
||||
|
||||
<ds-select
|
||||
icon="user"
|
||||
v-model="formData.groupType"
|
||||
label="Sichtbarkeit"
|
||||
:options="['public', 'closed', 'hidden']"
|
||||
placeholder="Status ..."
|
||||
></ds-select>
|
||||
|
||||
<ds-input v-model="formData.about" label="Kurzbeschreibung" rows="3"></ds-input>
|
||||
|
||||
<ds-input
|
||||
v-model="formData.description"
|
||||
label="Beschreibung"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
></ds-input>
|
||||
|
||||
<ds-select
|
||||
icon="card"
|
||||
v-model="formData.actionRadius"
|
||||
label="Radius"
|
||||
<option v-for="groupType in groupTypeOptions" :key="groupType" :value="groupType">
|
||||
{{ $t(`group.types.${groupType}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<!-- goal -->
|
||||
<ds-input
|
||||
:label="$t('group.goal')"
|
||||
v-model="formData.about"
|
||||
:placeholder="$t('group.goal') + ' …'"
|
||||
rows="3"
|
||||
></ds-input>
|
||||
<!-- description -->
|
||||
<ds-input
|
||||
:label="$t('group.description')"
|
||||
v-model="formData.description"
|
||||
:placeholder="$t('group.description') + ' …'"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
></ds-input>
|
||||
<ds-space margin-top="large">
|
||||
<!-- actionRadius -->
|
||||
<!-- TODO: move 'ds-select' from styleguide to main code and implement missen translation etc. functionality -->
|
||||
<!-- <ds-select
|
||||
id="actionRadius"
|
||||
:label="$t('group.actionRadius')"
|
||||
v-model="formData"
|
||||
model="actionRadius"
|
||||
:options="['regional', 'national', 'continental', 'global']"
|
||||
placeholder="Radius ..."
|
||||
></ds-select>
|
||||
icon="globe"
|
||||
:placeholder="`${$t('group.actionRadius')} …`"
|
||||
></ds-select> -->
|
||||
<ds-text class="select-label">
|
||||
{{ $t('group.actionRadius') }}
|
||||
</ds-text>
|
||||
<ds-icon class="select-icon" name="globe" />
|
||||
<select
|
||||
class="select"
|
||||
:options="actionRadiusOptions"
|
||||
:value="formData.actionRadius"
|
||||
@change="changeActionRadius($event)"
|
||||
>
|
||||
<option
|
||||
v-for="actionRadius in actionRadiusOptions"
|
||||
:key="actionRadius"
|
||||
:value="actionRadius"
|
||||
>
|
||||
{{ $t(`group.actionRadii.${actionRadius}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<!-- location -->
|
||||
<ds-select
|
||||
id="city"
|
||||
:label="$t('settings.data.labelCity')"
|
||||
v-model="formData.locationName"
|
||||
:options="cities"
|
||||
icon="map-marker"
|
||||
:placeholder="$t('settings.data.labelCity') + ' …'"
|
||||
:loading="loadingGeo"
|
||||
@input.native="handleCityInput"
|
||||
/>
|
||||
<!-- TODO: implement clear button -->
|
||||
<!-- <base-button icon="close" circle ghost size="small" :disabled="formData.locationName.length === 0" @click="clear" /> -->
|
||||
</ds-space>
|
||||
<ds-space margin-top="large">
|
||||
<categories-select
|
||||
v-if="categoriesActive"
|
||||
model="categoryIds"
|
||||
:existingCategoryIds="formData.categoryIds"
|
||||
/>
|
||||
|
||||
<div>{{ formData }}</div>
|
||||
|
||||
<ds-space margin-top="large">
|
||||
<ds-button @click.prevent="reset()">Reset form</ds-button>
|
||||
<ds-button
|
||||
type="submit"
|
||||
@click.prevent="submit()"
|
||||
icon="save"
|
||||
:disabled="disabled"
|
||||
primary
|
||||
>
|
||||
{{ update ? $t('group.update') : $t('group.save') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</ds-form>
|
||||
<ds-space centered v-show="!update">
|
||||
<nuxt-link to="/group/my-groups">zurück</nuxt-link>
|
||||
</ds-space>
|
||||
</ds-container>
|
||||
<ds-space margin-top="large">
|
||||
<nuxt-link to="/my-groups">
|
||||
<ds-button>{{ $t('actions.cancel') }}</ds-button>
|
||||
</nuxt-link>
|
||||
<ds-button
|
||||
type="submit"
|
||||
icon="save"
|
||||
:disabled="update ? submitDisableEdit : submitDisable"
|
||||
primary
|
||||
@click.prevent="submit()"
|
||||
>
|
||||
{{ update ? $t('group.update') : $t('group.save') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</ds-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -70,6 +134,9 @@
|
||||
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||
import { CATEGORIES_MIN, CATEGORIES_MAX } from '~/constants/categories.js'
|
||||
import { NAME_LENGTH_MIN, NAME_LENGTH_MAX } from '~/constants/groups.js'
|
||||
import { queryLocations } from '~/graphql/location'
|
||||
|
||||
let timeout
|
||||
|
||||
export default {
|
||||
name: 'GroupForm',
|
||||
@ -89,24 +156,33 @@ export default {
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const { name, groupType, about, description, actionRadius, categories } = this.group
|
||||
const { name, slug, groupType, about, description, actionRadius, locationName, categories } =
|
||||
this.group
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
disabled: false,
|
||||
groupTypeOptions: ['public', 'closed', 'hidden'],
|
||||
actionRadiusOptions: ['regional', 'national', 'continental', 'global'],
|
||||
loadingGeo: false,
|
||||
cities: [],
|
||||
formData: {
|
||||
name: name || '',
|
||||
slug: slug || '',
|
||||
groupType: groupType || '',
|
||||
about: about || '',
|
||||
description: description || '',
|
||||
locationName: locationName || '',
|
||||
actionRadius: actionRadius || '',
|
||||
categoryIds: categories ? categories.map((category) => category.id) : [],
|
||||
},
|
||||
formSchema: {
|
||||
name: { required: true, min: NAME_LENGTH_MIN, max: NAME_LENGTH_MAX },
|
||||
slug: { required: false },
|
||||
groupType: { required: true },
|
||||
about: { required: true },
|
||||
description: { required: true },
|
||||
actionRadius: { required: true },
|
||||
locationName: { required: false },
|
||||
categoryIds: {
|
||||
type: 'array',
|
||||
required: this.categoriesActive,
|
||||
@ -123,16 +199,54 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
submitDisable() {
|
||||
return (
|
||||
this.formData.name === '' ||
|
||||
this.formData.groupType === '' ||
|
||||
// this.formData.about === '' || // not mandatory
|
||||
this.formData.description === '' ||
|
||||
this.formData.actionRadius === '' ||
|
||||
// this.formData.locationName === '' || // not mandatory
|
||||
this.formData.categoryIds.length === 0
|
||||
)
|
||||
},
|
||||
submitDisableEdit() {
|
||||
return (
|
||||
this.formData.name === this.group.name &&
|
||||
this.formData.slug === this.group.slug &&
|
||||
// this.formData.groupType === this.group.groupType && // can not be changed for now
|
||||
this.formData.about === this.group.about &&
|
||||
this.formData.description === this.group.description &&
|
||||
this.formData.actionRadius === this.group.actionRadius &&
|
||||
this.formData.locationName === (this.group.locationName ? this.group.locationName : '') &&
|
||||
this.sameCategories
|
||||
)
|
||||
},
|
||||
sameCategories() {
|
||||
const formDataCategories = this.formData.categoryIds.map((id) => id).sort()
|
||||
const groupDataCategories = this.group.categories.map((category) => category.id).sort()
|
||||
let equal = true
|
||||
|
||||
if (formDataCategories.length !== groupDataCategories.length) return false
|
||||
|
||||
formDataCategories.forEach((id, index) => {
|
||||
equal = equal && id === groupDataCategories[index]
|
||||
})
|
||||
return equal
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
const { name, about, description, groupType, actionRadius, categoryIds } = this.formData
|
||||
const { name, about, description, groupType, actionRadius, locationName, categoryIds } =
|
||||
this.formData
|
||||
const variables = {
|
||||
name,
|
||||
about,
|
||||
description,
|
||||
groupType,
|
||||
actionRadius,
|
||||
locationName: locationName.label,
|
||||
categoryIds,
|
||||
}
|
||||
this.update
|
||||
@ -142,9 +256,64 @@ export default {
|
||||
})
|
||||
: this.$emit('createGroup', variables)
|
||||
},
|
||||
reset() {
|
||||
alert('reset')
|
||||
changeGroupType(event) {
|
||||
this.formData.groupType = event.target.value
|
||||
},
|
||||
changeActionRadius(event) {
|
||||
this.formData.actionRadius = event.target.value
|
||||
},
|
||||
handleCityInput(value) {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => this.requestGeoData(value), 500)
|
||||
},
|
||||
processLocationsResult(places) {
|
||||
if (!places.length) {
|
||||
return []
|
||||
}
|
||||
const result = []
|
||||
places.forEach((place) => {
|
||||
result.push({
|
||||
label: place.place_name,
|
||||
value: place.place_name,
|
||||
id: place.id,
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
},
|
||||
async requestGeoData(e) {
|
||||
const value = e.target ? e.target.value.trim() : ''
|
||||
if (value === '') {
|
||||
this.cities = []
|
||||
return
|
||||
}
|
||||
this.loadingGeo = true
|
||||
|
||||
const place = encodeURIComponent(value)
|
||||
const lang = this.$i18n.locale()
|
||||
|
||||
const {
|
||||
data: { queryLocations: res },
|
||||
} = await this.$apollo.query({ query: queryLocations(), variables: { place, lang } })
|
||||
|
||||
this.cities = this.processLocationsResult(res)
|
||||
this.loadingGeo = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-label {
|
||||
margin-bottom: 3pt;
|
||||
color: $text-color-soft;
|
||||
}
|
||||
.select-icon {
|
||||
margin-right: 4pt;
|
||||
color: $text-color-disabled;
|
||||
}
|
||||
.select {
|
||||
margin-bottom: $space-small;
|
||||
color: $text-color-base;
|
||||
}
|
||||
</style>
|
||||
|
||||
21
webapp/components/Group/GroupList.vue
Normal file
21
webapp/components/Group/GroupList.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space margin-bottom="small" v-for="group in groups" :key="group.id">
|
||||
<group-teaser :group="group" />
|
||||
</ds-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupTeaser from '~/components/Group/GroupTeaser'
|
||||
|
||||
export default {
|
||||
name: 'GroupList',
|
||||
components: {
|
||||
GroupTeaser,
|
||||
},
|
||||
props: {
|
||||
groups: { type: Array, default: () => [] },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,56 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space><h3>Members</h3></ds-space>
|
||||
<ds-table :data="GroupMembers" :fields="tableFields">
|
||||
<template slot="avatar">
|
||||
<ds-avatar online size="small" name="Hans Peter"></ds-avatar>
|
||||
<ds-table :fields="tableFields" :data="groupMembers" condensed>
|
||||
<template #avatar="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<ds-avatar online size="small" :name="scope.row.name"></ds-avatar>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="loves" slot-scope="scope">
|
||||
{{ scope.row.name }} loves {{ scope.row.loves }}
|
||||
<template #name="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<ds-text>
|
||||
<b>{{ scope.row.name | truncate(20) }}</b>
|
||||
</ds-text>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="edit" slot-scope="scope">
|
||||
<ds-button size="small" @click="deleteRow(scope.row)">delete</ds-button>
|
||||
<template #slug="scope">
|
||||
<nuxt-link
|
||||
:to="{
|
||||
name: 'profile-id-slug',
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<ds-text>
|
||||
<b>{{ `@${scope.row.slug}` | truncate(20) }}</b>
|
||||
</ds-text>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template #roleInGroup="scope">
|
||||
<select
|
||||
v-if="scope.row.myRoleInGroup !== 'owner'"
|
||||
:options="['pending', 'usual', 'admin', 'owner']"
|
||||
:value="`${scope.row.myRoleInGroup}`"
|
||||
@change="changeMemberRole(scope.row.id, $event)"
|
||||
>
|
||||
<option v-for="role in ['pending', 'usual', 'admin', 'owner']" :key="role" :value="role">
|
||||
{{ $t(`group.roles.${role}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<ds-chip v-else color="primary">
|
||||
{{ $t(`group.roles.${scope.row.myRoleInGroup}`) }}
|
||||
</ds-chip>
|
||||
</template>
|
||||
<template #edit="scope">
|
||||
<ds-button size="small" primary :disabled="true" @click="openModal(scope.row)">
|
||||
<!-- TODO: implement removal of group members -->
|
||||
<!-- :disabled="scope.row.myRoleInGroup === 'owner'"
|
||||
-->
|
||||
{{ $t('group.removeMemberButton') }}
|
||||
</ds-button>
|
||||
</template>
|
||||
</ds-table>
|
||||
<!-- TODO: implement removal of group members -->
|
||||
<!-- TODO: change to ocelot.social modal -->
|
||||
<!-- <ds-modal
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="`${$t('group.removeMember')}`"
|
||||
force
|
||||
extended
|
||||
:confirm-label="$t('group.removeMember')"
|
||||
:cancel-label="$t('actions.cancel')"
|
||||
@confirm="deleteMember(memberId)"
|
||||
/> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { changeGroupMemberRoleMutation } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
name: 'GroupMember',
|
||||
props: {
|
||||
groupId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
groupMembers: {
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableFields: ['avatar', 'name', 'type', 'loves', 'edit'],
|
||||
GroupMembers: [
|
||||
{
|
||||
name: 'Rengar',
|
||||
type: 'Jungler',
|
||||
loves: 'Hide and seek',
|
||||
},
|
||||
{
|
||||
name: 'Renekton',
|
||||
type: 'Toplaner',
|
||||
loves: 'Slice and dice',
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
type: 'ADC',
|
||||
loves: 'Spray and pray',
|
||||
},
|
||||
{
|
||||
name: 'Blitz',
|
||||
type: 'Support',
|
||||
loves: 'Hook you up',
|
||||
},
|
||||
],
|
||||
isOpen: false,
|
||||
memberId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteRow(row) {
|
||||
const index = this.tableData.indexOf(row)
|
||||
if (index > -1) {
|
||||
this.tableData.splice(index, 1)
|
||||
computed: {
|
||||
tableFields() {
|
||||
return {
|
||||
avatar: {
|
||||
label: this.$t('group.membersAdministrationList.avatar'),
|
||||
align: 'left',
|
||||
},
|
||||
name: {
|
||||
label: this.$t('group.membersAdministrationList.name'),
|
||||
align: 'left',
|
||||
},
|
||||
slug: {
|
||||
label: this.$t('group.membersAdministrationList.slug'),
|
||||
align: 'left',
|
||||
},
|
||||
roleInGroup: {
|
||||
label: this.$t('group.membersAdministrationList.roleInGroup'),
|
||||
align: 'left',
|
||||
},
|
||||
edit: {
|
||||
label: '',
|
||||
align: 'left',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async changeMemberRole(id, event) {
|
||||
const newRole = event.target.value
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: { groupId: this.groupId, userId: id, roleInGroup: newRole },
|
||||
})
|
||||
this.$toast.success(
|
||||
this.$t('group.changeMemberRole', { role: this.$t(`group.roles.${newRole}`) }),
|
||||
)
|
||||
} catch (error) {
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
},
|
||||
// TODO: implement removal of group members
|
||||
// openModal(row) {
|
||||
// this.isOpen = true
|
||||
// this.memberId = row.id
|
||||
// },
|
||||
// deleteMember(id) {
|
||||
// alert('deleteMember: ' + id)
|
||||
// },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,27 +1,178 @@
|
||||
<template>
|
||||
<div class="group-teaser">
|
||||
<ds-grid-item :row-span="2" column-span="fullWidth">
|
||||
<ds-space centered>
|
||||
<nuxt-link :to="{ name: 'group-create' }">
|
||||
<base-button
|
||||
<nuxt-link
|
||||
class="group-teaser"
|
||||
:to="{ name: 'group-id-slug', params: { id: group.id, slug: group.slug } }"
|
||||
>
|
||||
<base-card
|
||||
:class="{
|
||||
'disabled-content': group.disabled,
|
||||
}"
|
||||
>
|
||||
<h2 class="title hyphenate-text">{{ group.name }}</h2>
|
||||
<div class="slug-location">
|
||||
<!-- group slug -->
|
||||
<div>
|
||||
<ds-text color="soft">
|
||||
<base-icon name="at" />
|
||||
{{ group.slug }}
|
||||
</ds-text>
|
||||
</div>
|
||||
<!-- group location -->
|
||||
<div class="location-item">
|
||||
<ds-text v-if="group && group.location" color="soft">
|
||||
<base-icon name="map-marker" />
|
||||
{{ group && group.location ? group.location.name : '' }}
|
||||
</ds-text>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="content hyphenate-text" v-html="descriptionExcerpt" />
|
||||
<footer class="footer">
|
||||
<div>
|
||||
<!-- group my role in group -->
|
||||
<ds-chip color="primary">
|
||||
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
|
||||
</ds-chip>
|
||||
<!-- group type -->
|
||||
<ds-chip color="primary">
|
||||
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
|
||||
</ds-chip>
|
||||
<!-- group action radius -->
|
||||
<ds-chip color="primary">
|
||||
{{ group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : '' }}
|
||||
</ds-chip>
|
||||
</div>
|
||||
<!-- group categories -->
|
||||
<div class="categories" v-if="categoriesActive">
|
||||
<category
|
||||
v-for="category in group.categories"
|
||||
:key="category.id"
|
||||
v-tooltip="{
|
||||
content: $t('group.newGroup'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
:path="{ name: 'group-create' }"
|
||||
class="profile-post-add-button"
|
||||
icon="plus"
|
||||
circle
|
||||
filled
|
||||
:icon="category.icon"
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
</ds-grid-item>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="categories-placeholder"></div>
|
||||
<!-- group context menu -->
|
||||
<client-only>
|
||||
<group-content-menu resource-type="group" :resource="group" :group="group" />
|
||||
</client-only>
|
||||
</footer>
|
||||
<footer class="footer">
|
||||
<!-- group goal -->
|
||||
<div class="labeled-chip">
|
||||
<ds-text class="label-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.goal') }}
|
||||
</ds-text>
|
||||
<div class="chip">
|
||||
<ds-chip v-if="group && group.about">{{ group ? group.about : '' }}</ds-chip>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</base-card>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Category from '~/components/Category'
|
||||
import GroupContentMenu from '~/components/Group/GroupContentMenu'
|
||||
|
||||
export default {
|
||||
name: 'GroupTeaser',
|
||||
components: {
|
||||
Category,
|
||||
GroupContentMenu,
|
||||
},
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
descriptionExcerpt() {
|
||||
return this.$filters.removeLinks(this.group.descriptionExcerpt)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.group-teaser,
|
||||
.group-teaser:hover,
|
||||
.group-teaser:active {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 100%;
|
||||
color: $text-color-base;
|
||||
|
||||
> .ribbon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
.group-teaser > .base-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
> .title {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
> .slug-location {
|
||||
display: flex;
|
||||
margin-bottom: $space-small;
|
||||
|
||||
> .location-item {
|
||||
margin-left: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: $space-small;
|
||||
}
|
||||
|
||||
> .footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
> .categories-placeholder {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .labeled-chip {
|
||||
margin-top: $space-xx-small;
|
||||
|
||||
> .chip {
|
||||
margin-top: -$space-small + $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
> .content-menu {
|
||||
position: relative;
|
||||
z-index: $z-index-post-teaser-link;
|
||||
}
|
||||
}
|
||||
|
||||
.user-teaser {
|
||||
margin-bottom: $space-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -117,7 +117,7 @@ export default {
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: $space-xx-small;
|
||||
color: $text-color-soft;
|
||||
color: $color-locale-menu;
|
||||
|
||||
> .label {
|
||||
margin: 0 $space-xx-small;
|
||||
|
||||
@ -59,7 +59,11 @@ export default {
|
||||
},
|
||||
data() {
|
||||
const logosObject = {
|
||||
header: { path: logos.LOGO_HEADER_PATH, alt: 'Header', widthDefault: '130px' },
|
||||
header: {
|
||||
path: logos.LOGO_HEADER_PATH,
|
||||
alt: 'Header',
|
||||
widthDefault: logos.LOGO_HEADER_WIDTH,
|
||||
},
|
||||
welcome: { path: logos.LOGO_WELCOME_PATH, alt: 'Welcome', widthDefault: '200px' },
|
||||
signup: { path: logos.LOGO_SIGNUP_PATH, alt: 'Sign Up', widthDefault: '200px' },
|
||||
logout: { path: logos.LOGO_LOGOUT_PATH, alt: 'Logging Out', widthDefault: '200px' },
|
||||
@ -107,7 +111,6 @@ export default {
|
||||
}
|
||||
|
||||
.ds-logo-svg {
|
||||
width: 130px;
|
||||
height: auto;
|
||||
fill: #000000;
|
||||
}
|
||||
|
||||
@ -8,10 +8,4 @@
|
||||
|
||||
.ds-logo-inverse {
|
||||
color: $text-color-primary-inverse;
|
||||
}
|
||||
|
||||
.ds-logo-svg {
|
||||
width: 130px;
|
||||
height: auto;
|
||||
fill: currentColor;
|
||||
}
|
||||
@ -38,11 +38,14 @@ export default {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
background-color: $color-footer-background;
|
||||
width: 100%;
|
||||
padding: 10px 10px;
|
||||
box-shadow: 0px -6px 12px -4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.ds-footer a {
|
||||
color: $color-footer-link;
|
||||
}
|
||||
.division-line {
|
||||
margin-left: 0.2rem;
|
||||
margin-right: 0.2rem;
|
||||
|
||||
@ -19,9 +19,8 @@
|
||||
</client-only>
|
||||
<h2 class="title hyphenate-text">{{ post.title }}</h2>
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="content hyphenate-text" v-html="excerpt" />
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<footer
|
||||
class="footer"
|
||||
v-observe-visibility="(isVisible, entry) => visibilityChanged(isVisible, entry, post.id)"
|
||||
@ -31,9 +30,8 @@
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.name.${category.slug}`),
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
:icon="category.icon"
|
||||
/>
|
||||
|
||||
@ -29,6 +29,7 @@ describe('AvatarUploader', () => {
|
||||
profile: {
|
||||
avatar: { url: '/api/generic.jpg' },
|
||||
},
|
||||
updateMutation: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -40,7 +41,7 @@ describe('AvatarUploader', () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sends a the UpdateUser mutation when vddrop is called', () => {
|
||||
it('sends the UpdateUser mutation when vddrop is called', () => {
|
||||
wrapper.vm.vddrop([{ filename: 'avatar.jpg' }])
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="avatar-uploader">
|
||||
<vue-dropzone
|
||||
id="customdropzone"
|
||||
:key="avatarUrl"
|
||||
@ -29,7 +29,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
profile: { type: Object, required: true },
|
||||
updateMutation: { type: Object, required: true },
|
||||
updateMutation: { type: Function, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -69,7 +69,7 @@ export default {
|
||||
const avatarUpload = file[0]
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: this.updateMutation,
|
||||
mutation: this.updateMutation(),
|
||||
variables: {
|
||||
avatar: {
|
||||
upload: avatarUpload,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<button
|
||||
:class="buttonClass"
|
||||
:disabled="loading"
|
||||
:disabled="disabled || loading"
|
||||
:type="type"
|
||||
@click.capture="(event) => $emit('click', event)"
|
||||
>
|
||||
@ -46,7 +46,7 @@ export default {
|
||||
type: String,
|
||||
default: 'regular',
|
||||
validator(value) {
|
||||
return value.match(/(small|regular)/)
|
||||
return value.match(/(small|regular|large)/)
|
||||
},
|
||||
},
|
||||
type: {
|
||||
@ -56,6 +56,10 @@ export default {
|
||||
return value.match(/(button|submit)/)
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
// type: Boolean, // makes some errors that an Object was passed instead a Boolean and could not find how to solve in a acceptable time
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
buttonClass() {
|
||||
@ -66,6 +70,7 @@ export default {
|
||||
if (this.danger) buttonClass += ' --danger'
|
||||
if (this.loading) buttonClass += ' --loading'
|
||||
if (this.size === 'small') buttonClass += ' --small'
|
||||
if (this.size === 'large') buttonClass += ' --large'
|
||||
|
||||
if (this.filled) buttonClass += ' --filled'
|
||||
else if (this.ghost) buttonClass += ' --ghost'
|
||||
@ -123,6 +128,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
&.--large {
|
||||
height: $size-button-large;
|
||||
font-size: $font-size-large;
|
||||
|
||||
&.--circle {
|
||||
width: $size-button-large;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.--icon-only) > .base-icon {
|
||||
margin-right: $space-xx-small;
|
||||
}
|
||||
|
||||
@ -2,3 +2,4 @@
|
||||
export const NAME_LENGTH_MIN = 3
|
||||
export const NAME_LENGTH_MAX = 50
|
||||
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags
|
||||
export const SHOW_GROUP_BUTTON_IN_HEADER = true
|
||||
|
||||
13
webapp/constants/headerMenu.js
Normal file
13
webapp/constants/headerMenu.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default {
|
||||
MENU: [
|
||||
// {
|
||||
// name: 'Beiträge',
|
||||
// path: '/#',
|
||||
// },
|
||||
// {
|
||||
// name: 'Über Yunite',
|
||||
// path: '/#',
|
||||
// url: 'https://yunite.org',
|
||||
// },
|
||||
],
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
// this are the paths in the webapp
|
||||
export default {
|
||||
LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg',
|
||||
LOGO_HEADER_WIDTH: '130px',
|
||||
LOGO_SIGNUP_PATH: '/img/custom/logo-squared.svg',
|
||||
LOGO_WELCOME_PATH: '/img/custom/logo-squared.svg',
|
||||
LOGO_LOGOUT_PATH: '/img/custom/logo-squared.svg',
|
||||
|
||||
@ -12,6 +12,7 @@ export const userFragment = gql`
|
||||
deleted
|
||||
}
|
||||
`
|
||||
|
||||
export const locationAndBadgesFragment = (lang) => gql`
|
||||
fragment locationAndBadges on User {
|
||||
location {
|
||||
|
||||
@ -221,25 +221,25 @@ export const updateUserMutation = () => {
|
||||
$id: ID!
|
||||
$slug: String
|
||||
$name: String
|
||||
$locationName: String
|
||||
$about: String
|
||||
$allowEmbedIframes: Boolean
|
||||
$showShoutsPublicly: Boolean
|
||||
$sendNotificationEmails: Boolean
|
||||
$termsAndConditionsAgreedVersion: String
|
||||
$avatar: ImageInput
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
UpdateUser(
|
||||
id: $id
|
||||
slug: $slug
|
||||
name: $name
|
||||
locationName: $locationName
|
||||
about: $about
|
||||
allowEmbedIframes: $allowEmbedIframes
|
||||
showShoutsPublicly: $showShoutsPublicly
|
||||
sendNotificationEmails: $sendNotificationEmails
|
||||
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
slug
|
||||
|
||||
@ -2,170 +2,191 @@ import gql from 'graphql-tag'
|
||||
|
||||
// ------ mutations
|
||||
|
||||
export const createGroupMutation = gql`
|
||||
mutation (
|
||||
$id: ID
|
||||
$name: String!
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String!
|
||||
$groupType: GroupType!
|
||||
$actionRadius: GroupActionRadius!
|
||||
$categoryIds: [ID]
|
||||
$locationName: String
|
||||
) {
|
||||
CreateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
groupType: $groupType
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
locationName: $locationName
|
||||
export const createGroupMutation = () => {
|
||||
return gql`
|
||||
mutation (
|
||||
$id: ID
|
||||
$name: String!
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String!
|
||||
$groupType: GroupType!
|
||||
$actionRadius: GroupActionRadius!
|
||||
$categoryIds: [ID]
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
CreateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
groupType: $groupType
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
locationName
|
||||
myRole
|
||||
}
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const updateGroupMutation = gql`
|
||||
mutation (
|
||||
$id: ID!
|
||||
$name: String
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String
|
||||
$actionRadius: GroupActionRadius
|
||||
$categoryIds: [ID]
|
||||
$avatar: ImageInput
|
||||
$locationName: String
|
||||
) {
|
||||
UpdateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
export const updateGroupMutation = () => {
|
||||
return gql`
|
||||
mutation (
|
||||
$id: ID!
|
||||
$name: String
|
||||
$slug: String
|
||||
$about: String
|
||||
$description: String
|
||||
$actionRadius: GroupActionRadius
|
||||
$categoryIds: [ID]
|
||||
$avatar: ImageInput
|
||||
$locationName: String # empty string '' sets it to null
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
UpdateGroup(
|
||||
id: $id
|
||||
name: $name
|
||||
slug: $slug
|
||||
about: $about
|
||||
description: $description
|
||||
actionRadius: $actionRadius
|
||||
categoryIds: $categoryIds
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
# avatar # test this as result
|
||||
locationName
|
||||
myRole
|
||||
}
|
||||
# avatar # test this as result
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const joinGroupMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
JoinGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const joinGroupMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
JoinGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const leaveGroupMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
LeaveGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const leaveGroupMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!) {
|
||||
LeaveGroup(groupId: $groupId, userId: $userId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const changeGroupMemberRoleMutation = gql`
|
||||
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
|
||||
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const changeGroupMemberRoleMutation = () => {
|
||||
return gql`
|
||||
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
|
||||
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
// ------ queries
|
||||
|
||||
export const groupQuery = gql`
|
||||
query ($isMember: Boolean, $id: ID, $slug: String) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
export const groupQuery = (i18n) => {
|
||||
const lang = i18n ? i18n.locale().toUpperCase() : 'EN'
|
||||
return gql`
|
||||
query ($isMember: Boolean, $id: ID, $slug: String) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
slug
|
||||
createdAt
|
||||
updatedAt
|
||||
disabled
|
||||
deleted
|
||||
about
|
||||
description
|
||||
descriptionExcerpt
|
||||
groupType
|
||||
actionRadius
|
||||
categories {
|
||||
id
|
||||
slug
|
||||
name
|
||||
icon
|
||||
}
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
locationName
|
||||
location {
|
||||
name: name${lang}
|
||||
}
|
||||
myRole
|
||||
}
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
# locationName # test this as result
|
||||
myRole
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
export const groupMembersQuery = gql`
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
export const groupMembersQuery = () => {
|
||||
return gql`
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
myRoleInGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`
|
||||
}
|
||||
|
||||
@ -4,11 +4,39 @@
|
||||
<ds-container class="main-navigation-container" style="padding: 10px 10px">
|
||||
<div>
|
||||
<ds-flex class="main-navigation-flex">
|
||||
<ds-flex-item :width="{ base: '142px' }">
|
||||
<ds-flex-item :width="{ base: LOGOS.LOGO_HEADER_WIDTH }" style="margin-right: 20px">
|
||||
<nuxt-link :to="{ name: 'index' }" v-scroll-to="'.main-navigation'">
|
||||
<logo logoType="header" />
|
||||
</nuxt-link>
|
||||
</ds-flex-item>
|
||||
|
||||
<ds-flex-item
|
||||
v-for="item in menu"
|
||||
:key="item.name"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
:width="{ base: 'auto' }"
|
||||
style="margin-right: 20px"
|
||||
>
|
||||
<a v-if="item.url" :href="item.url" target="_blank">
|
||||
<ds-text size="large" bold>
|
||||
{{ item.name }}
|
||||
</ds-text>
|
||||
</a>
|
||||
<nuxt-link v-else :to="item.path">
|
||||
<ds-text size="large" bold>
|
||||
{{ item.name }}
|
||||
</ds-text>
|
||||
</nuxt-link>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
v-if="categoriesActive && isLoggedIn"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-grow: 0; flex-basis: auto; margin-right: 20px"
|
||||
>
|
||||
<client-only>
|
||||
<categories-menu></categories-menu>
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '40%', sm: '40%', md: '40%', lg: '0%' }"
|
||||
class="mobile-hamburger-menu"
|
||||
@ -16,16 +44,12 @@
|
||||
<base-button icon="bars" @click="toggleMobileMenuView" circle />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
v-if="categoriesActive && isLoggedIn"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-grow: 0; flex-basis: auto"
|
||||
>
|
||||
<client-only>
|
||||
<categories-menu></categories-menu>
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '45%', sm: '45%', md: '45%', lg: '50%' }"
|
||||
:width="{
|
||||
base: '45%',
|
||||
sm: '45%',
|
||||
md: isHeaderMenu ? 'auto' : '45%',
|
||||
lg: isHeaderMenu ? 'auto' : '50%',
|
||||
}"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-shrink: 0; flex-grow: 1"
|
||||
id="nav-search-box"
|
||||
@ -43,7 +67,7 @@
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
style="background-color: white; flex-basis: auto"
|
||||
style="flex-basis: auto"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
>
|
||||
<div
|
||||
@ -64,6 +88,9 @@
|
||||
<invite-button placement="top" />
|
||||
</client-only>
|
||||
</div>
|
||||
<client-only v-if="SHOW_GROUP_BUTTON_IN_HEADER">
|
||||
<group-button />
|
||||
</client-only>
|
||||
<client-only>
|
||||
<avatar-menu placement="top" />
|
||||
</client-only>
|
||||
@ -88,18 +115,22 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '~/components/Logo/Logo'
|
||||
import { mapGetters } from 'vuex'
|
||||
import Logo from '~/components/Logo/Logo'
|
||||
import { SHOW_GROUP_BUTTON_IN_HEADER } from '~/constants/groups.js'
|
||||
import headerMenu from '~/constants/headerMenu.js'
|
||||
import LOGOS from '~/constants/logos.js'
|
||||
import seo from '~/mixins/seo'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import SearchField from '~/components/features/SearchField/SearchField.vue'
|
||||
import Modal from '~/components/Modal'
|
||||
import NotificationMenu from '~/components/NotificationMenu/NotificationMenu'
|
||||
import seo from '~/mixins/seo'
|
||||
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import PageFooter from '~/components/PageFooter/PageFooter'
|
||||
import AvatarMenu from '~/components/AvatarMenu/AvatarMenu'
|
||||
import CategoriesMenu from '~/components/FilterMenu/CategoriesMenu'
|
||||
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import GroupButton from '~/components/Group/GroupButton'
|
||||
import InviteButton from '~/components/InviteButton/InviteButton'
|
||||
import CategoriesMenu from '~/components/FilterMenu/CategoriesMenu.vue'
|
||||
import NotificationMenu from '~/components/NotificationMenu/NotificationMenu'
|
||||
import PageFooter from '~/components/PageFooter/PageFooter'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -113,10 +144,15 @@ export default {
|
||||
PageFooter,
|
||||
InviteButton,
|
||||
CategoriesMenu,
|
||||
GroupButton,
|
||||
},
|
||||
mixins: [seo],
|
||||
data() {
|
||||
return {
|
||||
LOGOS,
|
||||
SHOW_GROUP_BUTTON_IN_HEADER,
|
||||
isHeaderMenu: headerMenu.MENU.length > 0,
|
||||
menu: headerMenu.MENU,
|
||||
mobileSearchVisible: false,
|
||||
toggleMobileMenu: false,
|
||||
inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling,
|
||||
@ -141,6 +177,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.main-navigation {
|
||||
background-color: $color-header-background;
|
||||
}
|
||||
.margin-right-20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.margin-x {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.topbar-locale-switch {
|
||||
display: flex;
|
||||
margin-right: $space-xx-small;
|
||||
@ -156,11 +203,6 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main-navigation {
|
||||
a {
|
||||
color: $text-color-soft;
|
||||
}
|
||||
}
|
||||
.main-navigation-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@ -217,6 +217,27 @@
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} von {max} Themen ausgewählt"
|
||||
},
|
||||
"category": {
|
||||
"description": {
|
||||
"body-and-excercise": "Sport, Yoga, Massage, Tanzen, Entspannung",
|
||||
"children": "Familie, Pädagogik, Schule, Prägung",
|
||||
"culture": "Kunst, Theater, Musik, Fotografie, Film",
|
||||
"economy": "Handel, Konsum, Marketing, Lieferketten",
|
||||
"energy": "Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie",
|
||||
"finance": "Geld, Finanzsystem, Alternativwährungen",
|
||||
"health": "Medizin, Ernährung, WHO, Impfungen, Schadstoffe",
|
||||
"home": "Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten",
|
||||
"it-and-media": "Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, KI, Software, Apps",
|
||||
"law": "Menschenrechte, Gesetze, Verordnungen",
|
||||
"miscellaneous": "Sonstiges",
|
||||
"mobility": "Reise, Verkehr, Elektromobilität",
|
||||
"nature": "Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt",
|
||||
"networking": "Kooperation, Aktionsbündnisse, Solidarität, Hilfe",
|
||||
"peace": "Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken",
|
||||
"politics": "Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien",
|
||||
"psyche": "Seele, Gefühle, Glück",
|
||||
"science": "Bildung, Hochschule, Publikationen",
|
||||
"spirituality": "Religion, Werte, Ethik"
|
||||
},
|
||||
"name": {
|
||||
"body-and-excercise": "Körper & Bewegung",
|
||||
"children": "Kinder",
|
||||
@ -383,22 +404,50 @@
|
||||
"regional": "Regionale Gruppe"
|
||||
},
|
||||
"actionRadius": "Aktionsradius",
|
||||
"categories": "Thema ::: Themen",
|
||||
"changeMemberRole": "Die Rolle wurde auf „{role}“ geändert!",
|
||||
"contentMenu": {
|
||||
"visitGroupPage": "Gruppe anzeigen"
|
||||
},
|
||||
"createNewGroup": {
|
||||
"title": "Erstelle eine neue Gruppe",
|
||||
"tooltip": "Erstelle eine neue Gruppe"
|
||||
},
|
||||
"description": "Beschreibung",
|
||||
"editGroupSettings": {
|
||||
"groupName": "Einstellungen für „{name}“",
|
||||
"title": "Meine Gruppe ändern"
|
||||
},
|
||||
"follow": "Folge",
|
||||
"foundation": "Gründung",
|
||||
"general": "Allgemein",
|
||||
"goal": "Ziel der Gruppe",
|
||||
"group-created": "Die Gruppe wurde angelegt!",
|
||||
"group-updated": "Die Gruppendaten wurden geändert!",
|
||||
"groupCreated": "Die Gruppe wurde angelegt!",
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": "Bin Mitglied",
|
||||
"join": "Beitreten"
|
||||
},
|
||||
"labelSlug": "Eindeutiger Gruppenname",
|
||||
"leaveModal": {
|
||||
"confirmButton": "Verlassen",
|
||||
"message": "Eine Gruppe zu verlassen ist möglicherweise nicht rückgängig zu machen!<br>Gruppe <b>„{name}“</b> verlassen!",
|
||||
"title": "Möchtest du wirklich die Gruppe verlassen?"
|
||||
},
|
||||
"membersCount": "Mitglieder",
|
||||
"members": "Mitglieder",
|
||||
"membersAdministrationList": {
|
||||
"avatar": "Avatar",
|
||||
"name": "Name",
|
||||
"roleInGroup": "Rolle",
|
||||
"slug": "Eindeutiger Name"
|
||||
},
|
||||
"membersCount": "Mitglied ::: Mitglieder",
|
||||
"membersListTitle": "Gruppenmitglieder",
|
||||
"newGroup": "Erstelle eine neue Gruppe",
|
||||
"membersListTitleNotAllowedSeeingGroupMembers": "Gruppenmitglieder unsichtbar",
|
||||
"myGroups": "Meine Gruppen",
|
||||
"name": "Gruppenname",
|
||||
"radius": "Radius",
|
||||
"removeMember": "Mitglied aus der Gruppe entfernen?",
|
||||
"removeMemberButton": "Entfernen",
|
||||
"role": "Deine Rolle in der Gruppe",
|
||||
"roles": {
|
||||
"admin": "Administrator",
|
||||
@ -413,13 +462,20 @@
|
||||
"hidden": "Versteckte Gruppe",
|
||||
"public": "Öffentliche Gruppe"
|
||||
},
|
||||
"update": "Änderung speichern"
|
||||
"update": "Änderung speichern",
|
||||
"updatedGroup": "Die Gruppendaten wurden geändert!"
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Suche löschen",
|
||||
"hashtag-search": "Suche nach #{hashtag}",
|
||||
"title": "Deine Filterblase"
|
||||
},
|
||||
"header": {
|
||||
"avatarMenu": {
|
||||
"myGroups": "Mein Gruppen",
|
||||
"myProfile": "Mein Profil"
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"change-filter-settings": "Verändere die Filter-Einstellungen, um mehr Ergebnisse zu erhalten.",
|
||||
"no-results": "Keine Beiträge gefunden."
|
||||
@ -578,7 +634,6 @@
|
||||
"title": "Lade jemanden zu {APPLICATION_NAME} ein!"
|
||||
},
|
||||
"memberSince": "Mitglied seit",
|
||||
"name": "Mein Profil",
|
||||
"network": {
|
||||
"andMore": "und {number} weitere …",
|
||||
"followedBy": "wird gefolgt von:",
|
||||
@ -790,7 +845,6 @@
|
||||
"unmute": "Stummschaltung aufheben",
|
||||
"unmuted": "{name} ist nicht mehr stummgeschaltet"
|
||||
},
|
||||
"myGroups": "Meine Gruppen",
|
||||
"name": "Einstellungen",
|
||||
"notifications": {
|
||||
"name": "Benachrichtigungen",
|
||||
|
||||
@ -217,6 +217,27 @@
|
||||
"infoSelectedNoOfMaxCategories": "{chosen} of {max} topics selected"
|
||||
},
|
||||
"category": {
|
||||
"description": {
|
||||
"body-and-excercise": "Sports, yoga, massage, relaxation",
|
||||
"children": "Family, education, school, imprinting",
|
||||
"culture": "Art, theatre, music, photography, film",
|
||||
"economy": "Trade, consumption, marketing, supply chains",
|
||||
"energy": "Oil, gas, coal, wind, hydrodynamic power, biogas, atomic energy",
|
||||
"finance": "Money, financial system, alternative currencies",
|
||||
"health": "Medicine, nutrition, WHO, vaccination, harmful substances",
|
||||
"home": "Construction, communities, tiny houses, kitchen garden",
|
||||
"it-and-media": "News, manipulation, privacy, control, data leeches, AI, software, apps",
|
||||
"law": "Human rights, laws, decrees",
|
||||
"miscellaneous": "Miscellaneous",
|
||||
"mobility": "Travel, traffic, electric mobility",
|
||||
"nature": "Animals, plants, agriculture, ecologu, biodiversity",
|
||||
"networking": "Cooperation, action alliances, solidarity, help",
|
||||
"peace": "War, military, social defence, arms, cyber attacks",
|
||||
"politics": "Democracy, participation, elections, corruption, parties",
|
||||
"psyche": "Soul, feelings, happiness",
|
||||
"science": "Education, university, publications",
|
||||
"spirituality": "Religion, values, ethics"
|
||||
},
|
||||
"name": {
|
||||
"body-and-excercise": "Body & Excercise",
|
||||
"children": "Children",
|
||||
@ -383,22 +404,50 @@
|
||||
"regional": "Regional Group"
|
||||
},
|
||||
"actionRadius": "Action radius",
|
||||
"categories": "Topic ::: Topics",
|
||||
"changeMemberRole": "The role has been changed to “{role}”!",
|
||||
"contentMenu": {
|
||||
"visitGroupPage": "Show group"
|
||||
},
|
||||
"createNewGroup": {
|
||||
"title": "Create A New Group",
|
||||
"tooltip": "Create a new group"
|
||||
},
|
||||
"description": "Description",
|
||||
"editGroupSettings": {
|
||||
"groupName": "Settings Of “{name}”",
|
||||
"title": "Edit My Group"
|
||||
},
|
||||
"follow": "Follow",
|
||||
"foundation": "Foundation",
|
||||
"general": "General",
|
||||
"goal": "Goal of group",
|
||||
"group-created": "The group was created!",
|
||||
"group-updated": "The group data has been changed.",
|
||||
"groupCreated": "The group was created!",
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": "I'm a member",
|
||||
"join": "Join"
|
||||
},
|
||||
"labelSlug": "Unique group name",
|
||||
"leaveModal": {
|
||||
"confirmButton": "Leave",
|
||||
"message": "Leaving a group may be irreversible!<br>Leave group <b>“{name}”</b>!",
|
||||
"title": "Do you really want to leave the group?"
|
||||
},
|
||||
"membersCount": "Members",
|
||||
"members": "Members",
|
||||
"membersAdministrationList": {
|
||||
"avatar": "Avatar",
|
||||
"name": "Name",
|
||||
"roleInGroup": "Role",
|
||||
"slug": "Unique name"
|
||||
},
|
||||
"membersCount": "Member ::: Members",
|
||||
"membersListTitle": "Group Members",
|
||||
"newGroup": "Create a new Group",
|
||||
"membersListTitleNotAllowedSeeingGroupMembers": "Group Members invisible",
|
||||
"myGroups": "My Groups",
|
||||
"name": "Group name",
|
||||
"radius": "Radius",
|
||||
"removeMember": "Remove member",
|
||||
"removeMemberButton": "Remove",
|
||||
"role": "Your role in the group",
|
||||
"roles": {
|
||||
"admin": "Administrator",
|
||||
@ -413,13 +462,20 @@
|
||||
"hidden": "Hidden Group",
|
||||
"public": "Public Group"
|
||||
},
|
||||
"update": "Save change"
|
||||
"update": "Save change",
|
||||
"updatedGroup": "The group data has been changed."
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Clear search",
|
||||
"hashtag-search": "Searching for #{hashtag}",
|
||||
"title": "Your filter bubble"
|
||||
},
|
||||
"header": {
|
||||
"avatarMenu": {
|
||||
"myGroups": "My groups",
|
||||
"myProfile": "My profile"
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"change-filter-settings": "Change your filter settings to get more results.",
|
||||
"no-results": "No contributions found."
|
||||
@ -578,7 +634,6 @@
|
||||
"title": "Invite somebody to {APPLICATION_NAME}!"
|
||||
},
|
||||
"memberSince": "Member since",
|
||||
"name": "My Profile",
|
||||
"network": {
|
||||
"andMore": "and {number} more …",
|
||||
"followedBy": "is followed by:",
|
||||
@ -790,7 +845,6 @@
|
||||
"unmute": "Unmute user",
|
||||
"unmuted": "{name} is unmuted again"
|
||||
},
|
||||
"myGroups": "My Groups",
|
||||
"name": "Settings",
|
||||
"notifications": {
|
||||
"name": "Notifications",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ocelot-social/maintenance",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "Maintenance page for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -91,13 +91,21 @@ export default {
|
||||
/*
|
||||
** Global CSS
|
||||
*/
|
||||
css: ['~assets/_new/styles/resets.scss', '~assets/styles/main.scss'],
|
||||
css: [
|
||||
'~assets/_new/styles/resets.scss',
|
||||
'~assets/styles/main.scss',
|
||||
'~assets/styles/imports/_branding.scss',
|
||||
],
|
||||
|
||||
/*
|
||||
** Global processed styles
|
||||
*/
|
||||
styleResources: {
|
||||
scss: [styleguideStyles, '~assets/_new/styles/tokens.scss'],
|
||||
scss: [
|
||||
styleguideStyles,
|
||||
'~assets/_new/styles/tokens.scss',
|
||||
'~assets/styles/imports/_branding.scss',
|
||||
],
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-webapp",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "ocelot.social Frontend",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
// import { config, mount } from '@vue/test-utils'
|
||||
// import ProfileSlug from './_slug.vue'
|
||||
|
||||
// const localVue = global.localVue
|
||||
|
||||
// localVue.filter('date', (d) => d)
|
||||
|
||||
// config.stubs['client-only'] = '<span><slot /></span>'
|
||||
// config.stubs['v-popover'] = '<span><slot /></span>'
|
||||
// config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
// config.stubs['infinite-loading'] = '<span><slot /></span>'
|
||||
// config.stubs['follow-list'] = '<span><slot /></span>'
|
||||
|
||||
// describe('ProfileSlug', () => {
|
||||
// let wrapper
|
||||
// let Wrapper
|
||||
// let mocks
|
||||
|
||||
// beforeEach(() => {
|
||||
// mocks = {
|
||||
// post: {
|
||||
// id: 'p23',
|
||||
// name: 'It is a post',
|
||||
// },
|
||||
// $t: jest.fn(),
|
||||
// // If you're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
|
||||
// $route: {
|
||||
// params: {
|
||||
// id: '4711',
|
||||
// slug: 'john-doe',
|
||||
// },
|
||||
// },
|
||||
// $router: {
|
||||
// history: {
|
||||
// push: jest.fn(),
|
||||
// },
|
||||
// },
|
||||
// $toast: {
|
||||
// success: jest.fn(),
|
||||
// error: jest.fn(),
|
||||
// },
|
||||
// $apollo: {
|
||||
// loading: false,
|
||||
// mutate: jest.fn().mockResolvedValue(),
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
|
||||
// describe('mount', () => {
|
||||
// Wrapper = () => {
|
||||
// return mount(ProfileSlug, {
|
||||
// mocks,
|
||||
// localVue,
|
||||
// })
|
||||
// }
|
||||
|
||||
// describe('given an authenticated user', () => {
|
||||
// beforeEach(() => {
|
||||
// mocks.$filters = {
|
||||
// removeLinks: (c) => c,
|
||||
// truncate: (a) => a,
|
||||
// }
|
||||
// mocks.$store = {
|
||||
// getters: {
|
||||
// 'auth/isModerator': () => false,
|
||||
// 'auth/user': {
|
||||
// id: 'u23',
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
|
||||
// describe('given a user for the profile', () => {
|
||||
// beforeEach(() => {
|
||||
// wrapper = Wrapper()
|
||||
// wrapper.setData({
|
||||
// User: [
|
||||
// {
|
||||
// id: 'u3',
|
||||
// name: 'Bob the builder',
|
||||
// contributionsCount: 6,
|
||||
// shoutedCount: 7,
|
||||
// commentedCount: 8,
|
||||
// },
|
||||
// ],
|
||||
// })
|
||||
// })
|
||||
|
||||
// it('displays name of the user', () => {
|
||||
// expect(wrapper.text()).toContain('Bob the builder')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
1574
webapp/pages/group/_id/_slug.spec.js
Normal file
1574
webapp/pages/group/_id/_slug.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="isGroupVisible">
|
||||
<ds-space />
|
||||
<ds-flex v-if="group" :width="{ base: '100%' }" gutter="base">
|
||||
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
|
||||
@ -31,24 +31,30 @@
|
||||
/>
|
||||
</client-only> -->
|
||||
<ds-space margin="small">
|
||||
<!-- group name -->
|
||||
<ds-heading tag="h3" align="center" no-margin>
|
||||
{{ groupName }}
|
||||
</ds-heading>
|
||||
<!-- group slug -->
|
||||
<ds-text align="center" color="soft">
|
||||
<base-icon name="at" data-test="at" />
|
||||
{{ groupSlug }}
|
||||
</ds-text>
|
||||
<!-- <ds-text v-if="user.location" align="center" color="soft" size="small">
|
||||
<base-icon name="map-marker" />
|
||||
{{ user.location.name }}
|
||||
</ds-text> -->
|
||||
<!-- group location -->
|
||||
<ds-text v-if="group && group.location" align="center" color="soft" size="small">
|
||||
<base-icon name="map-marker" data-test="map-marker" />
|
||||
{{ group && group.location ? group.location.name : '' }}
|
||||
</ds-text>
|
||||
<!-- group created at -->
|
||||
<ds-text align="center" color="soft" size="small">
|
||||
{{ $t('group.foundation') }} {{ group.createdAt | date('MMMM yyyy') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<ds-flex v-if="isAllowedSeeingGroupMembers">
|
||||
<!-- group members count -->
|
||||
<ds-flex-item v-if="isAllowedSeeingGroupMembers">
|
||||
<client-only>
|
||||
<ds-number :label="$t('group.membersCount')">
|
||||
<ds-number :label="$t('group.membersCount', {}, groupMembers.length)">
|
||||
<count-to
|
||||
slot="count"
|
||||
:start-val="membersCountStartValue"
|
||||
@ -90,6 +96,7 @@
|
||||
@optimistic="optimisticFollow"
|
||||
@update="updateFollow"
|
||||
/> -->
|
||||
<!-- Group join / leave -->
|
||||
<join-leave-button
|
||||
:group="group || {}"
|
||||
:userId="currentUser.id"
|
||||
@ -104,35 +111,82 @@
|
||||
</div>
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<!-- group my role in group -->
|
||||
<template v-if="isGroupMember">
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.role') }}
|
||||
</ds-text>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">{{ $t('group.roles.' + group.myRole) }}</ds-chip>
|
||||
<ds-chip color="primary">
|
||||
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
|
||||
</ds-chip>
|
||||
</div>
|
||||
</template>
|
||||
<!-- group type -->
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.type') }}
|
||||
</ds-text>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">{{ $t('group.types.' + group.groupType) }}</ds-chip>
|
||||
<ds-chip color="primary">
|
||||
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
|
||||
</ds-chip>
|
||||
</div>
|
||||
<!-- group action radius -->
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.actionRadius') }}
|
||||
</ds-text>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">{{ $t('group.actionRadii.' + group.actionRadius) }}</ds-chip>
|
||||
<ds-chip color="primary">
|
||||
{{
|
||||
group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : ''
|
||||
}}
|
||||
</ds-chip>
|
||||
</div>
|
||||
<ds-space margin="x-small" />
|
||||
</ds-space>
|
||||
<template v-if="group.about">
|
||||
<!-- group categories -->
|
||||
<template v-if="categoriesActive">
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{
|
||||
$t(
|
||||
'group.categories',
|
||||
{},
|
||||
group && group.categories ? group.categories.length : 0,
|
||||
)
|
||||
}}
|
||||
</ds-text>
|
||||
<ds-space margin="xx-small" />
|
||||
<div class="categories">
|
||||
<div
|
||||
v-for="(category, index) in group.categories"
|
||||
:key="category.id"
|
||||
align="center"
|
||||
>
|
||||
<category
|
||||
:icon="category.icon"
|
||||
:name="$t(`contribution.category.name.${category.slug}`)"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
/>
|
||||
<ds-space v-if="index < group.categories.length - 1" margin="xxx-small" />
|
||||
</div>
|
||||
</div>
|
||||
</ds-space>
|
||||
</template>
|
||||
<!-- group goal -->
|
||||
<template v-if="group && group.about">
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.goal') }}
|
||||
</ds-text>
|
||||
<ds-space margin="xx-small" />
|
||||
<div class="chip" align="center">
|
||||
<ds-chip>{{ group.about }}</ds-chip>
|
||||
<ds-chip>{{ group ? group.about : '' }}</ds-chip>
|
||||
</div>
|
||||
</ds-space>
|
||||
</template>
|
||||
@ -143,10 +197,15 @@
|
||||
</ds-heading>
|
||||
<!-- Group members list -->
|
||||
<profile-list
|
||||
:uniqueName="`groupMembersFilter`"
|
||||
:uniqueName="'groupMembersFilter'"
|
||||
:title="$t('group.membersListTitle')"
|
||||
:allProfilesCount="groupMembers.length"
|
||||
:profiles="groupMembers"
|
||||
:titleNobody="
|
||||
!isAllowedSeeingGroupMembers
|
||||
? $t('group.membersListTitleNotAllowedSeeingGroupMembers')
|
||||
: null
|
||||
"
|
||||
:allProfilesCount="isAllowedSeeingGroupMembers ? groupMembers.length : 0"
|
||||
:profiles="isAllowedSeeingGroupMembers ? groupMembers : []"
|
||||
:loading="$apollo.loading"
|
||||
@fetchAllProfiles="fetchAllMembers"
|
||||
/>
|
||||
@ -168,31 +227,47 @@
|
||||
</ds-flex-item>
|
||||
|
||||
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
|
||||
<!-- Group description -->
|
||||
<ds-space>
|
||||
<base-card class="group-description">
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div
|
||||
v-if="isDescriptionCollapsed"
|
||||
class="content hyphenate-text"
|
||||
v-html="groupDescriptionExcerpt"
|
||||
/>
|
||||
<content-viewer v-else class="content hyphenate-text" :content="group.description" />
|
||||
<base-button
|
||||
class="collaps-button"
|
||||
size="small"
|
||||
ghost
|
||||
@click="isDescriptionCollapsed = !isDescriptionCollapsed"
|
||||
>
|
||||
{{ isDescriptionCollapsed ? $t('comment.show.more') : $t('comment.show.less') }}
|
||||
</base-button>
|
||||
</base-card>
|
||||
</ds-space>
|
||||
<ds-space v-if="isGroupMemberNonePending" centered>
|
||||
<nuxt-link :to="{ name: 'post-create' }">
|
||||
<base-button
|
||||
class="profile-post-add-button"
|
||||
:path="{ name: 'post-create' }"
|
||||
icon="plus"
|
||||
circle
|
||||
filled
|
||||
v-tooltip="{
|
||||
content: $t('contribution.newPost'),
|
||||
placement: 'left',
|
||||
}"
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
<masonry-grid>
|
||||
<!-- TapNavigation -->
|
||||
<!-- <tab-navigation :tabs="tabOptions" :activeTab="tabActive" @switch-tab="handleTab" /> -->
|
||||
|
||||
<!-- feed -->
|
||||
<ds-grid-item :row-span="2" column-span="fullWidth">
|
||||
<ds-space centered>
|
||||
<nuxt-link :to="{ name: 'post-create' }">
|
||||
<base-button
|
||||
v-if="isGroupMember"
|
||||
v-tooltip="{
|
||||
content: $t('contribution.newPost'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
:path="{ name: 'post-create' }"
|
||||
class="profile-post-add-button"
|
||||
icon="plus"
|
||||
circle
|
||||
filled
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
</ds-grid-item>
|
||||
|
||||
<!-- Group post feed -->
|
||||
<template v-if="posts && posts.length">
|
||||
<masonry-grid-item
|
||||
v-for="post in posts"
|
||||
@ -217,7 +292,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<ds-grid-item column-span="fullWidth">
|
||||
<empty margin="xx-large" icon="file" />
|
||||
<empty margin="xx-large" icon="file" data-test="icon-empty" />
|
||||
</ds-grid-item>
|
||||
</template>
|
||||
</masonry-grid>
|
||||
@ -238,7 +313,9 @@ import { updateGroupMutation, groupQuery, groupMembersQuery } from '~/graphql/gr
|
||||
// import UpdateQuery from '~/components/utils/UpdateQuery'
|
||||
import postListActions from '~/mixins/postListActions'
|
||||
import AvatarUploader from '~/components/Uploader/AvatarUploader'
|
||||
import Category from '~/components/Category'
|
||||
// import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import CountTo from '~/components/CountTo.vue'
|
||||
import Empty from '~/components/Empty/Empty'
|
||||
// import FollowButton from '~/components/Button/FollowButton'
|
||||
@ -263,7 +340,9 @@ import ProfileList from '~/components/features/ProfileList/ProfileList'
|
||||
export default {
|
||||
components: {
|
||||
AvatarUploader,
|
||||
Category,
|
||||
// ContentMenu,
|
||||
ContentViewer,
|
||||
CountTo,
|
||||
Empty,
|
||||
// FollowButton,
|
||||
@ -290,8 +369,10 @@ export default {
|
||||
data() {
|
||||
// const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
|
||||
return {
|
||||
categoriesActive: this.$env.CATEGORIES_ACTIVE,
|
||||
Group: [],
|
||||
GroupMembers: [],
|
||||
loadGroupMembers: false,
|
||||
posts: [],
|
||||
// hasMore: true,
|
||||
// offset: 0,
|
||||
@ -304,6 +385,7 @@ export default {
|
||||
membersCountStartValue: 0,
|
||||
membersCountToLoad: Infinity,
|
||||
updateGroupMutation,
|
||||
isDescriptionCollapsed: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -311,16 +393,7 @@ export default {
|
||||
return this.$store.getters['auth/user']
|
||||
},
|
||||
group() {
|
||||
return this.Group[0] ? this.Group[0] : {}
|
||||
},
|
||||
groupMembers() {
|
||||
return this.GroupMembers ? this.GroupMembers : []
|
||||
},
|
||||
isGroupOwner() {
|
||||
return this.group ? this.group.myRole === 'owner' : false
|
||||
},
|
||||
isGroupMember() {
|
||||
return this.group ? !!this.group.myRole : false
|
||||
return this.Group && this.Group[0] ? this.Group[0] : {}
|
||||
},
|
||||
groupName() {
|
||||
const { name } = this.group || {}
|
||||
@ -328,7 +401,32 @@ export default {
|
||||
},
|
||||
groupSlug() {
|
||||
const { slug } = this.group || {}
|
||||
return slug && `@${slug}`
|
||||
return slug
|
||||
},
|
||||
groupDescriptionExcerpt() {
|
||||
return this.group ? this.$filters.removeLinks(this.group.descriptionExcerpt) : ''
|
||||
},
|
||||
isGroupOwner() {
|
||||
return this.group ? this.group.myRole === 'owner' : false
|
||||
},
|
||||
isGroupMember() {
|
||||
return this.group ? !!this.group.myRole : false
|
||||
},
|
||||
isGroupMemberNonePending() {
|
||||
return this.group ? ['usual', 'admin', 'owner'].includes(this.group.myRole) : false
|
||||
},
|
||||
isGroupVisible() {
|
||||
return this.group && !(this.group.groupType === 'hidden' && !this.isGroupMemberNonePending)
|
||||
},
|
||||
groupMembers() {
|
||||
return this.GroupMembers ? this.GroupMembers : []
|
||||
},
|
||||
isAllowedSeeingGroupMembers() {
|
||||
return (
|
||||
this.group &&
|
||||
(this.group.groupType === 'public' ||
|
||||
(['closed', 'hidden'].includes(this.group.groupType) && this.isGroupMemberNonePending))
|
||||
)
|
||||
},
|
||||
// tabOptions() {
|
||||
// return [
|
||||
@ -353,6 +451,11 @@ export default {
|
||||
// ]
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
isAllowedSeeingGroupMembers(to, _from) {
|
||||
this.loadGroupMembers = to
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// handleTab(tab) {
|
||||
// if (this.tabActive !== tab) {
|
||||
@ -463,7 +566,11 @@ export default {
|
||||
},
|
||||
updateJoinLeave({ myRoleInGroup }) {
|
||||
this.Group[0].myRole = myRoleInGroup
|
||||
this.$apollo.queries.GroupMembers.refetch()
|
||||
if (this.isAllowedSeeingGroupMembers) {
|
||||
this.$apollo.queries.GroupMembers.refetch()
|
||||
} else {
|
||||
this.GroupMembers = []
|
||||
}
|
||||
},
|
||||
fetchAllMembers() {
|
||||
this.membersCountToLoad = Infinity
|
||||
@ -489,8 +596,7 @@ export default {
|
||||
// },
|
||||
Group: {
|
||||
query() {
|
||||
// return groupQuery(this.$i18n) // language will be needed for locations
|
||||
return groupQuery
|
||||
return groupQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
@ -499,17 +605,26 @@ export default {
|
||||
// followingCount: this.followingCount,
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
GroupMembers: {
|
||||
query() {
|
||||
return groupMembersQuery
|
||||
return groupMembersQuery()
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
id: this.$route.params.id,
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return !this.loadGroupMembers
|
||||
},
|
||||
error(error) {
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
@ -547,4 +662,17 @@ export default {
|
||||
.chip {
|
||||
margin-bottom: $space-x-small;
|
||||
}
|
||||
.group-description > .base-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
> .content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: $space-small;
|
||||
}
|
||||
}
|
||||
.collaps-button {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,26 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Create Groupe</h2>
|
||||
<ds-flex :width="{ base: '100%' }" gutter="base">
|
||||
<ds-flex-item :width="{ base: '100%', md: 5 }">
|
||||
<group-form @createGroup="createGroup" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
</ds-flex>
|
||||
<hr />
|
||||
<group-member />
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('group.createNewGroup.title') }}</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-container>
|
||||
<base-card>
|
||||
<ds-space margin="large">
|
||||
<ds-flex :width="{ base: '100%' }" gutter="base">
|
||||
<ds-flex-item :width="{ base: '100%', md: 5 }">
|
||||
<ds-container>
|
||||
<group-form @createGroup="createGroup" />
|
||||
</ds-container>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
</base-card>
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupForm from '~/components/Group/GroupForm'
|
||||
import GroupMember from '~/components/Group/GroupMember'
|
||||
import { createGroupMutation } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GroupForm,
|
||||
GroupMember,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -29,14 +36,33 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async createGroup(value) {
|
||||
const { name, about, description, groupType, actionRadius, categoryIds } = value
|
||||
const variables = { name, about, description, groupType, actionRadius, categoryIds }
|
||||
const { name, about, description, groupType, actionRadius, locationName, categoryIds } = value
|
||||
const variables = {
|
||||
name,
|
||||
about,
|
||||
description,
|
||||
groupType,
|
||||
actionRadius,
|
||||
locationName,
|
||||
categoryIds,
|
||||
}
|
||||
let responseId, responseSlug
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: createGroupMutation,
|
||||
mutation: createGroupMutation(),
|
||||
variables,
|
||||
update: (_store, { data }) => {
|
||||
const { id: groupId, slug: groupSlug } = data.CreateGroup
|
||||
responseId = groupId
|
||||
responseSlug = groupSlug
|
||||
},
|
||||
})
|
||||
this.$toast.success(this.$t('group.groupCreated'))
|
||||
// this.$router.history.push('/my-groups')
|
||||
this.$router.history.push({
|
||||
name: 'group-id-slug',
|
||||
params: { id: responseId, slug: responseSlug },
|
||||
})
|
||||
this.$toast.success(this.$t('group.group-created'))
|
||||
} catch (error) {
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-page-title heading="Group Setting"></ds-page-title>
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('group.editGroupSettings.title') }}</ds-heading>
|
||||
<ds-heading tag="h2">
|
||||
{{ $t('group.editGroupSettings.groupName', { name: group.name }) }}
|
||||
</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
@ -11,9 +17,6 @@
|
||||
</transition>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<ds-space centered>
|
||||
<nuxt-link to="/group/my-groups">zurück</nuxt-link>
|
||||
</ds-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -29,11 +32,11 @@ export default {
|
||||
routes() {
|
||||
return [
|
||||
{
|
||||
name: 'General',
|
||||
name: this.$t('group.general'),
|
||||
path: `/group/edit/${this.group.id}`,
|
||||
},
|
||||
{
|
||||
name: 'Members',
|
||||
name: this.$t('group.members'),
|
||||
path: `/group/edit/${this.group.id}/members`,
|
||||
},
|
||||
]
|
||||
@ -51,7 +54,7 @@ export default {
|
||||
Group: [group],
|
||||
},
|
||||
} = await client.query({
|
||||
query: groupQuery,
|
||||
query: groupQuery(), // "this.$i18n" is undefined here, so we use default lang
|
||||
variables: { id },
|
||||
})
|
||||
if (group.myRole !== 'owner') {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-container>
|
||||
<base-card>
|
||||
<ds-heading tag="h3">{{ $t('group.general') }}</ds-heading>
|
||||
<ds-space margin="large" />
|
||||
<group-form :group="group" :update="true" @updateGroup="updateGroup" />
|
||||
</ds-container>
|
||||
</base-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -23,14 +25,44 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async updateGroup(value) {
|
||||
const { id, name, about, description, groupType, actionRadius, categoryIds } = value
|
||||
const variables = { id, name, about, description, groupType, actionRadius, categoryIds }
|
||||
const {
|
||||
id,
|
||||
slug,
|
||||
name,
|
||||
about,
|
||||
description,
|
||||
groupType,
|
||||
actionRadius,
|
||||
locationName,
|
||||
categoryIds,
|
||||
} = value
|
||||
const variables = {
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
about,
|
||||
description,
|
||||
groupType,
|
||||
actionRadius,
|
||||
locationName,
|
||||
categoryIds,
|
||||
}
|
||||
let responseId, responseSlug
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: updateGroupMutation,
|
||||
mutation: updateGroupMutation(),
|
||||
variables,
|
||||
update: (_store, { data }) => {
|
||||
const { id: groupId, slug: groupSlug } = data.UpdateGroup
|
||||
responseId = groupId
|
||||
responseSlug = groupSlug
|
||||
},
|
||||
})
|
||||
this.$toast.success(this.$t('group.updatedGroup'))
|
||||
this.$router.history.push({
|
||||
name: 'group-id-slug',
|
||||
params: { id: responseId, slug: responseSlug },
|
||||
})
|
||||
this.$toast.success(this.$t('group.group-updated'))
|
||||
} catch (error) {
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
|
||||
@ -1,16 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-container>
|
||||
<group-member />
|
||||
</ds-container>
|
||||
<base-card>
|
||||
<ds-heading tag="h3">{{ $t('group.members') }}</ds-heading>
|
||||
<ds-space margin="large" />
|
||||
<group-member :groupId="group.id" :groupMembers="groupMembers" />
|
||||
</base-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupMember from '~/components/Group/GroupMember'
|
||||
import { groupMembersQuery } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GroupMember,
|
||||
},
|
||||
props: {
|
||||
group: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
GroupMembers: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
groupMembers() {
|
||||
return this.GroupMembers ? this.GroupMembers : []
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
GroupMembers: {
|
||||
query() {
|
||||
return groupMembersQuery()
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
id: this.group.id,
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
this.GroupMembers = []
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -38,7 +38,6 @@
|
||||
v-tooltip="{
|
||||
content: $t('contribution.newPost'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
class="post-add-button"
|
||||
icon="plus"
|
||||
|
||||
@ -1,44 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>my groups</div>
|
||||
<group-teaser />
|
||||
<br />
|
||||
<br />
|
||||
<group-card :items="responseGroupListQuery" />
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('group.myGroups') }}</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-container>
|
||||
<!-- create group -->
|
||||
<ds-space centered>
|
||||
<nuxt-link :to="{ name: 'group-create' }">
|
||||
<base-button
|
||||
class="group-add-button"
|
||||
icon="plus"
|
||||
size="large"
|
||||
circle
|
||||
filled
|
||||
v-tooltip="{
|
||||
content: $t('group.createNewGroup.tooltip'),
|
||||
placement: 'left',
|
||||
}"
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
<!-- group list -->
|
||||
<group-list :groups="myGroups" />
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupTeaser from '~/components/Group/GroupTeaser.vue'
|
||||
import GroupCard from '~/components/Group/GroupCard.vue'
|
||||
import GroupList from '~/components/Group/GroupList'
|
||||
import { groupQuery } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
name: 'MyGroups',
|
||||
components: {
|
||||
GroupTeaser,
|
||||
GroupCard,
|
||||
GroupList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
responseGroupListQuery: [],
|
||||
Group: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async groupListQuery() {
|
||||
try {
|
||||
const response = await this.$apollo.query({
|
||||
query: groupQuery,
|
||||
})
|
||||
this.responseGroupListQuery = response.data.Group
|
||||
} catch (error) {
|
||||
this.responseGroupListQuery = []
|
||||
} finally {
|
||||
this.pending = false
|
||||
}
|
||||
computed: {
|
||||
myGroups() {
|
||||
return this.Group ? this.Group : []
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.groupListQuery()
|
||||
apollo: {
|
||||
Group: {
|
||||
query() {
|
||||
return groupQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
isMember: true,
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
this.Group = []
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-add-button {
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -46,7 +46,6 @@
|
||||
<content-viewer class="content hyphenate-text" :content="post.content" />
|
||||
<!-- Categories -->
|
||||
<div v-if="categoriesActive" class="categories">
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ds-space margin="xx-large" />
|
||||
<ds-space margin="xx-small" />
|
||||
<hc-category
|
||||
@ -54,6 +53,10 @@
|
||||
:key="category.id"
|
||||
:icon="category.icon"
|
||||
:name="$t(`contribution.category.name.${category.slug}`)"
|
||||
v-tooltip="{
|
||||
content: $t(`contribution.category.description.${category.slug}`),
|
||||
placement: 'bottom-start',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<ds-space margin-bottom="small" />
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
{{ userName }}
|
||||
</ds-heading>
|
||||
<ds-text align="center" color="soft">
|
||||
<base-icon name="at" />
|
||||
{{ userSlug }}
|
||||
</ds-text>
|
||||
<ds-text v-if="user.location" align="center" color="soft" size="small">
|
||||
@ -120,7 +121,6 @@
|
||||
v-tooltip="{
|
||||
content: $t('contribution.newPost'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
:path="{ name: 'post-create' }"
|
||||
class="profile-post-add-button"
|
||||
@ -238,7 +238,7 @@ export default {
|
||||
followedByCountStartValue: 0,
|
||||
followedByCount: followListVisibleCount,
|
||||
followingCount: followListVisibleCount,
|
||||
updateUserMutation: updateUserMutation(),
|
||||
updateUserMutation,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -254,7 +254,7 @@ export default {
|
||||
},
|
||||
userSlug() {
|
||||
const { slug } = this.user || {}
|
||||
return slug && `@${slug}`
|
||||
return slug
|
||||
},
|
||||
tabOptions() {
|
||||
return [
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-heading tag="h1">{{ $t('settings.name') }}</ds-heading>
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('settings.name') }}</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
@ -39,10 +42,6 @@ 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`,
|
||||
|
||||
@ -48,6 +48,7 @@ import { queryLocations } from '~/graphql/location'
|
||||
let timeout
|
||||
|
||||
export default {
|
||||
name: 'NewsFeed',
|
||||
data() {
|
||||
return {
|
||||
cities: [],
|
||||
|
||||
@ -25,6 +25,7 @@ import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||
import { updateUserMutation } from '~/graphql/User.js'
|
||||
|
||||
export default {
|
||||
name: 'TermsAndConditionsConfirm',
|
||||
layout: 'default',
|
||||
head() {
|
||||
return {
|
||||
|
||||
@ -3,7 +3,7 @@ import VTooltip from 'v-tooltip'
|
||||
|
||||
Vue.use(VTooltip, {
|
||||
defaultDelay: {
|
||||
show: 500,
|
||||
show: 750,
|
||||
hide: 50,
|
||||
},
|
||||
defaultOffset: 2,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user