mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #5335 from Ocelot-Social-Community/5059-groups/5318-group-profile-members-list-etc
feat: 🍰 Group Profile Members List Etc
This commit is contained in:
commit
6f59ced9eb
@ -106,6 +106,17 @@ export const joinGroupMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const leaveGroupMutation = 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) {
|
||||
@ -149,8 +160,8 @@ export const groupQuery = gql`
|
||||
`
|
||||
|
||||
export const groupMembersQuery = gql`
|
||||
query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) {
|
||||
GroupMembers(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
|
||||
@ -191,6 +191,36 @@ const isAllowedToJoinGroup = rule({
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedToLeaveGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
if (!(user && user.id)) return false
|
||||
const { groupId, userId } = args
|
||||
if (user.id !== userId) return false
|
||||
const session = driver.session()
|
||||
const readTxPromise = session.readTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
RETURN group {.*}, member {.*, myRoleInGroup: membership.role}
|
||||
`,
|
||||
{ groupId, userId },
|
||||
)
|
||||
return {
|
||||
group: transactionResponse.records.map((record) => record.get('group'))[0],
|
||||
member: transactionResponse.records.map((record) => record.get('member'))[0],
|
||||
}
|
||||
})
|
||||
try {
|
||||
const { group, member } = await readTxPromise
|
||||
return !!group && !!member && !!member.myRoleInGroup && member.myRoleInGroup !== 'owner'
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAuthor = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
@ -284,6 +314,7 @@ export default shield(
|
||||
CreateGroup: isAuthenticated,
|
||||
UpdateGroup: isAllowedToChangeGroupSettings,
|
||||
JoinGroup: isAllowedToJoinGroup,
|
||||
LeaveGroup: isAllowedToLeaveGroup,
|
||||
ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
|
||||
@ -244,6 +244,27 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
LeaveGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
const { groupId, userId } = params
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const leaveGroupCypher = `
|
||||
MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||
DELETE membership
|
||||
RETURN member {.*, myRoleInGroup: NULL}
|
||||
`
|
||||
const transactionResponse = await transaction.run(leaveGroupCypher, { groupId, userId })
|
||||
const [member] = await transactionResponse.records.map((record) => record.get('member'))
|
||||
return member
|
||||
})
|
||||
try {
|
||||
return await writeTxResultPromise
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => {
|
||||
const { groupId, userId, roleInGroup } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
createGroupMutation,
|
||||
updateGroupMutation,
|
||||
joinGroupMutation,
|
||||
leaveGroupMutation,
|
||||
changeGroupMemberRoleMutation,
|
||||
groupMembersQuery,
|
||||
groupQuery,
|
||||
@ -17,6 +18,12 @@ const neode = getNeode()
|
||||
|
||||
let authenticatedUser
|
||||
let user
|
||||
let noMemberUser
|
||||
let pendingMemberUser
|
||||
let usualMemberUser
|
||||
let adminMemberUser
|
||||
let ownerMemberUser
|
||||
let secondOwnerMemberUser
|
||||
|
||||
const categoryIds = ['cat9', 'cat4', 'cat15']
|
||||
const descriptionAdditional100 =
|
||||
@ -76,6 +83,169 @@ const seedBasicsAndClearAuthentication = async () => {
|
||||
authenticatedUser = null
|
||||
}
|
||||
|
||||
const seedComplexScenarioAndClearAuthentication = async () => {
|
||||
await seedBasicsAndClearAuthentication()
|
||||
// create users
|
||||
noMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'none-member-user',
|
||||
name: 'None Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'none-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
pendingMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'pending-member-user',
|
||||
name: 'Pending Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'pending-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
usualMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'usual-member-user',
|
||||
name: 'Usual Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'usual-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
adminMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'admin-member-user',
|
||||
name: 'Admin Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'admin-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
ownerMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'owner-member-user',
|
||||
name: 'Owner Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'owner-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondOwnerMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'second-owner-member-user',
|
||||
name: 'Second Owner Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'second-owner-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
// create groups
|
||||
// public-group
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'public-group',
|
||||
name: 'The Best Group',
|
||||
about: 'We will change the world!',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'public',
|
||||
actionRadius: 'regional',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation,
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'owner-of-closed-group',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation,
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'owner-of-hidden-group',
|
||||
},
|
||||
})
|
||||
// closed-group
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'closed-group',
|
||||
name: 'Uninteresting Group',
|
||||
about: 'We will change nothing!',
|
||||
description: 'We love it like it is!?' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
// hidden-group
|
||||
authenticatedUser = await adminMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'hidden-group',
|
||||
name: 'Investigative Journalism Group',
|
||||
about: 'We will change all.',
|
||||
description: 'We research …' + descriptionAdditional100,
|
||||
groupType: 'hidden',
|
||||
actionRadius: 'global',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
// 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
|
||||
authenticatedUser = null
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
@ -1032,8 +1202,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1065,8 +1235,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1098,8 +1268,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1141,8 +1311,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1174,8 +1344,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1239,8 +1409,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1276,8 +1446,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1313,8 +1483,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
it('finds all members', async () => {
|
||||
const result = await mutate({
|
||||
mutation: groupMembersQuery,
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables,
|
||||
})
|
||||
expect(result).toMatchObject({
|
||||
@ -1371,162 +1541,8 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
describe('ChangeGroupMemberRole', () => {
|
||||
let pendingMemberUser
|
||||
let usualMemberUser
|
||||
let adminMemberUser
|
||||
let ownerMemberUser
|
||||
let secondOwnerMemberUser
|
||||
|
||||
beforeAll(async () => {
|
||||
await seedBasicsAndClearAuthentication()
|
||||
// create users
|
||||
pendingMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'pending-member-user',
|
||||
name: 'Pending Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'pending-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
usualMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'usual-member-user',
|
||||
name: 'Usual Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'usual-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
adminMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'admin-member-user',
|
||||
name: 'Admin Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'admin-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
ownerMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'owner-member-user',
|
||||
name: 'Owner Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'owner-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondOwnerMemberUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'second-owner-member-user',
|
||||
name: 'Second Owner Member TestUser',
|
||||
},
|
||||
{
|
||||
email: 'second-owner-member-user@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
// create groups
|
||||
// public-group
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'public-group',
|
||||
name: 'The Best Group',
|
||||
about: 'We will change the world!',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'public',
|
||||
actionRadius: 'regional',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation,
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'owner-of-closed-group',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: joinGroupMutation,
|
||||
variables: {
|
||||
groupId: 'public-group',
|
||||
userId: 'owner-of-hidden-group',
|
||||
},
|
||||
})
|
||||
// closed-group
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'closed-group',
|
||||
name: 'Uninteresting Group',
|
||||
about: 'We will change nothing!',
|
||||
description: 'We love it like it is!?' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
// hidden-group
|
||||
authenticatedUser = await adminMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: createGroupMutation,
|
||||
variables: {
|
||||
id: 'hidden-group',
|
||||
name: 'Investigative Journalism Group',
|
||||
about: 'We will change all.',
|
||||
description: 'We research …' + descriptionAdditional100,
|
||||
groupType: 'hidden',
|
||||
actionRadius: 'global',
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
// 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'hidden-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
|
||||
authenticatedUser = null
|
||||
await seedComplexScenarioAndClearAuthentication()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -2330,6 +2346,241 @@ describe('in mode', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('LeaveGroup', () => {
|
||||
beforeAll(async () => {
|
||||
await seedComplexScenarioAndClearAuthentication()
|
||||
// closed-group
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'pending-member-user',
|
||||
roleInGroup: 'pending',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'usual-member-user',
|
||||
roleInGroup: 'usual',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'admin-member-user',
|
||||
roleInGroup: 'admin',
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation,
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: 'second-owner-member-user',
|
||||
roleInGroup: 'owner',
|
||||
},
|
||||
})
|
||||
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
groupId: 'not-existing-group',
|
||||
userId: 'current-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
describe('in all group types', () => {
|
||||
describe('here "closed-group" for example', () => {
|
||||
const memberInGroup = async (userId, groupId) => {
|
||||
const result = await query({
|
||||
query: groupMembersQuery,
|
||||
variables: {
|
||||
id: groupId,
|
||||
},
|
||||
})
|
||||
return result.data && result.data.GroupMembers
|
||||
? !!result.data.GroupMembers.find((member) => member.id === userId)
|
||||
: null
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
variables = {
|
||||
groupId: 'closed-group',
|
||||
}
|
||||
})
|
||||
|
||||
describe('left by "pending-member-user"', () => {
|
||||
it('has "null" as membership role, was in the group, and left the group', async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('pending-member-user', 'closed-group')).toBe(true)
|
||||
authenticatedUser = await pendingMemberUser.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'pending-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
LeaveGroup: {
|
||||
id: 'pending-member-user',
|
||||
myRoleInGroup: null,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('pending-member-user', 'closed-group')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('left by "usual-member-user"', () => {
|
||||
it('has "null" as membership role, was in the group, and left the group', async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('usual-member-user', 'closed-group')).toBe(true)
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
LeaveGroup: {
|
||||
id: 'usual-member-user',
|
||||
myRoleInGroup: null,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('usual-member-user', 'closed-group')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('left by "admin-member-user"', () => {
|
||||
it('has "null" as membership role, was in the group, and left the group', async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('admin-member-user', 'closed-group')).toBe(true)
|
||||
authenticatedUser = await adminMemberUser.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
LeaveGroup: {
|
||||
id: 'admin-member-user',
|
||||
myRoleInGroup: null,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
expect(await memberInGroup('admin-member-user', 'closed-group')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('left by "owner-member-user"', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'owner-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('left by "second-owner-member-user"', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await secondOwnerMemberUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'second-owner-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('left by "none-member-user"', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await noMemberUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'none-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('as "owner-member-user" try to leave member "usual-member-user"', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await ownerMemberUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('as "usual-member-user" try to leave member "admin-member-user"', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = await usualMemberUser.toJson()
|
||||
const { errors } = await mutate({
|
||||
mutation: leaveGroupMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdateGroup', () => {
|
||||
beforeAll(async () => {
|
||||
await seedBasicsAndClearAuthentication()
|
||||
|
||||
@ -117,6 +117,11 @@ type Mutation {
|
||||
userId: ID!
|
||||
): User
|
||||
|
||||
LeaveGroup(
|
||||
groupId: ID!
|
||||
userId: ID!
|
||||
): User
|
||||
|
||||
ChangeGroupMemberRole(
|
||||
groupId: ID!
|
||||
userId: ID!
|
||||
|
||||
@ -19,7 +19,6 @@ import { followUserMutation, unfollowUserMutation } from '~/graphql/User'
|
||||
|
||||
export default {
|
||||
name: 'HcFollowButton',
|
||||
|
||||
props: {
|
||||
followId: { type: String, default: null },
|
||||
isFollowed: { type: Boolean, default: false },
|
||||
136
webapp/components/Button/JoinLeaveButton.vue
Normal file
136
webapp/components/Button/JoinLeaveButton.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<base-button
|
||||
class="track-button"
|
||||
:disabled="disabled"
|
||||
:loading="localLoading"
|
||||
:icon="icon"
|
||||
:filled="isMember && !hovered"
|
||||
:danger="isMember && hovered"
|
||||
@mouseenter.native="onHover"
|
||||
@mouseleave.native="hovered = false"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
{{ label }}
|
||||
</base-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import { joinGroupMutation, leaveGroupMutation } from '~/graphql/groups'
|
||||
|
||||
export default {
|
||||
name: 'JoinLeaveButton',
|
||||
props: {
|
||||
group: { type: Object, required: true },
|
||||
userId: { type: String, required: true },
|
||||
isMember: { type: Boolean, required: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localLoading: this.loading,
|
||||
hovered: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
if (this.isMember && this.hovered) {
|
||||
return 'close'
|
||||
} else {
|
||||
return this.isMember ? 'check' : 'plus'
|
||||
}
|
||||
},
|
||||
label() {
|
||||
if (this.isMember) {
|
||||
return this.$t('group.joinLeaveButton.iAmMember')
|
||||
} else {
|
||||
return this.$t('group.joinLeaveButton.join')
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isMember() {
|
||||
this.localLoading = false
|
||||
this.hovered = false
|
||||
},
|
||||
loading() {
|
||||
this.localLoading = this.loading
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
commitModalData: 'modal/SET_OPEN',
|
||||
}),
|
||||
onHover() {
|
||||
if (!this.disabled && !this.localLoading) {
|
||||
this.hovered = true
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (this.isMember) {
|
||||
this.openLeaveModal()
|
||||
} else {
|
||||
this.joinLeave()
|
||||
}
|
||||
},
|
||||
openLeaveModal() {
|
||||
this.commitModalData(this.leaveModalData())
|
||||
},
|
||||
leaveModalData() {
|
||||
return {
|
||||
name: 'confirm',
|
||||
data: {
|
||||
type: '',
|
||||
resource: { id: '' },
|
||||
modalData: {
|
||||
titleIdent: 'group.leaveModal.title',
|
||||
messageIdent: 'group.leaveModal.message',
|
||||
messageParams: {
|
||||
name: this.group.name,
|
||||
},
|
||||
buttons: {
|
||||
confirm: {
|
||||
danger: true,
|
||||
icon: 'sign-out',
|
||||
textIdent: 'group.leaveModal.confirmButton',
|
||||
callback: this.joinLeave,
|
||||
},
|
||||
cancel: {
|
||||
icon: 'close',
|
||||
textIdent: 'actions.cancel',
|
||||
callback: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
async joinLeave() {
|
||||
const join = !this.isMember
|
||||
const mutation = join ? joinGroupMutation : leaveGroupMutation
|
||||
|
||||
this.hovered = false
|
||||
this.$emit('prepare', join)
|
||||
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation,
|
||||
variables: { groupId: this.group.id, userId: this.userId },
|
||||
})
|
||||
const joinedLeftGroupResult = join ? data.JoinGroup : data.LeaveGroup
|
||||
this.$emit('update', joinedLeftGroupResult)
|
||||
} catch (error) {
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.track-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
41
webapp/components/features/ProfileList/FollowList.vue
Normal file
41
webapp/components/features/ProfileList/FollowList.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<profile-list
|
||||
:uniqueName="`${type}Filter`"
|
||||
:title="$filters.truncate(userName, 15) + ' ' + $t(`profile.network.${type}`)"
|
||||
:titleNobody="$filters.truncate(userName, 15) + ' ' + $t(`profile.network.${type}Nobody`)"
|
||||
:allProfilesCount="allConnectionsCount"
|
||||
:profiles="connections"
|
||||
:loading="loading"
|
||||
@fetchAllProfiles="$emit('fetchAllConnections', type)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProfileList, { profileListVisibleCount } from '~/components/features/ProfileList/ProfileList'
|
||||
|
||||
export const followListVisibleCount = profileListVisibleCount
|
||||
|
||||
export default {
|
||||
name: 'FollowerList',
|
||||
components: {
|
||||
ProfileList,
|
||||
},
|
||||
props: {
|
||||
user: { type: Object, default: null },
|
||||
type: { type: String, default: 'following' },
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
allConnectionsCount() {
|
||||
return this.user[`${this.type}Count`]
|
||||
},
|
||||
connections() {
|
||||
return this.user[this.type]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<base-card class="follow-list">
|
||||
<template v-if="connections && connections.length">
|
||||
<base-card class="profile-list">
|
||||
<template v-if="profiles.length">
|
||||
<h5 class="title spacer-x-small">
|
||||
{{ userName | truncate(15) }} {{ $t(`profile.network.${type}`) }}
|
||||
{{ title }}
|
||||
</h5>
|
||||
<ul :class="connectionsClass">
|
||||
<ul :class="profilesClass">
|
||||
<li
|
||||
v-for="connection in filteredConnections"
|
||||
:key="connection.id"
|
||||
@ -13,30 +13,30 @@
|
||||
<user-teaser :user="connection" />
|
||||
</li>
|
||||
</ul>
|
||||
<base-button
|
||||
v-if="hasMore"
|
||||
:loading="loading"
|
||||
class="spacer-x-small"
|
||||
size="small"
|
||||
@click="$emit('fetchAllConnections', type)"
|
||||
>
|
||||
{{
|
||||
$t('profile.network.andMore', {
|
||||
number: allConnectionsCount - connections.length,
|
||||
})
|
||||
}}
|
||||
</base-button>
|
||||
<ds-input
|
||||
v-if="!hasMore"
|
||||
:name="`${type}Filter`"
|
||||
v-if="isMoreAsVisible"
|
||||
:name="uniqueName"
|
||||
:placeholder="filter"
|
||||
class="spacer-x-small"
|
||||
icon="filter"
|
||||
size="small"
|
||||
@input.native="setFilter"
|
||||
/>
|
||||
<base-button
|
||||
v-if="hasMore"
|
||||
:loading="loading"
|
||||
class="spacer-x-small"
|
||||
size="small"
|
||||
@click="$emit('fetchAllProfiles')"
|
||||
>
|
||||
{{
|
||||
$t('profile.network.andMore', {
|
||||
number: allProfilesCount - profiles.length,
|
||||
})
|
||||
}}
|
||||
</base-button>
|
||||
</template>
|
||||
<p v-else class="nobody-message">{{ userName }} {{ $t(`profile.network.${type}Nobody`) }}</p>
|
||||
<p v-else-if="titleNobody" class="nobody-message">{{ titleNobody }}</p>
|
||||
</base-card>
|
||||
</template>
|
||||
|
||||
@ -44,41 +44,40 @@
|
||||
import { escape } from 'xregexp/xregexp-all.js'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
|
||||
export const profileListVisibleCount = 7
|
||||
|
||||
export default {
|
||||
name: 'FollowerList',
|
||||
name: 'ProfileList',
|
||||
components: {
|
||||
UserTeaser,
|
||||
},
|
||||
props: {
|
||||
user: { type: Object, default: null },
|
||||
type: { type: String, default: 'following' },
|
||||
uniqueName: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
titleNobody: { type: String, default: null },
|
||||
allProfilesCount: { type: Number, required: true },
|
||||
profiles: { type: Array, required: true },
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
profileListVisibleCount,
|
||||
filter: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userName() {
|
||||
const { name } = this.user || {}
|
||||
return name || this.$t('profile.userAnonym')
|
||||
},
|
||||
allConnectionsCount() {
|
||||
return this.user[`${this.type}Count`]
|
||||
},
|
||||
connections() {
|
||||
return this.user[this.type]
|
||||
},
|
||||
hasMore() {
|
||||
return this.allConnectionsCount > this.connections.length
|
||||
return this.allProfilesCount > this.profiles.length
|
||||
},
|
||||
connectionsClass() {
|
||||
return `connections${this.hasMore ? '' : ' --overflow'}`
|
||||
isMoreAsVisible() {
|
||||
return this.profiles.length > this.profileListVisibleCount
|
||||
},
|
||||
profilesClass() {
|
||||
return `profiles${this.isMoreAsVisible ? ' --overflow' : ''}`
|
||||
},
|
||||
filteredConnections() {
|
||||
if (!this.filter) {
|
||||
return this.connections
|
||||
return this.profiles
|
||||
}
|
||||
|
||||
// @example
|
||||
@ -89,7 +88,7 @@ export default {
|
||||
'i',
|
||||
)
|
||||
|
||||
const fuzzyScores = this.connections
|
||||
const fuzzyScores = this.profiles
|
||||
.map((user) => {
|
||||
const match = user.name.match(fuzzyExpression)
|
||||
|
||||
@ -122,7 +121,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.follow-list {
|
||||
.profile-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
@ -133,7 +132,7 @@ export default {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
.connections {
|
||||
.profiles {
|
||||
height: $size-height-connections;
|
||||
padding: $space-none;
|
||||
list-style-type: none;
|
||||
@ -106,6 +106,17 @@ export const joinGroupMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const leaveGroupMutation = 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) {
|
||||
@ -149,8 +160,8 @@ export const groupQuery = gql`
|
||||
`
|
||||
|
||||
export const groupMembersQuery = gql`
|
||||
query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) {
|
||||
GroupMembers(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
|
||||
query ($id: ID!) {
|
||||
GroupMembers(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
|
||||
@ -375,10 +375,44 @@
|
||||
"following": "Folge Ich"
|
||||
},
|
||||
"group": {
|
||||
"actionRadii": {
|
||||
"continental": "Kontinentale Gruppe",
|
||||
"global": "Globale Gruppe",
|
||||
"interplanetary": "Interplanetare Gruppe",
|
||||
"national": "Nationale Gruppe",
|
||||
"regional": "Regionale Gruppe"
|
||||
},
|
||||
"actionRadius": "Aktionsradius",
|
||||
"foundation": "Gründung",
|
||||
"goal": "Ziel der Gruppe",
|
||||
"group-created": "Die Gruppe wurde angelegt!",
|
||||
"group-updated": "Die Gruppendaten wurden geändert!",
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": "Bin Mitglied",
|
||||
"join": "Beitreten"
|
||||
},
|
||||
"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",
|
||||
"membersListTitle": "Gruppenmitglieder",
|
||||
"newGroup": "Erstelle eine neue Gruppe",
|
||||
"role": "Deine Rolle in der Gruppe",
|
||||
"roles": {
|
||||
"admin": "Administrator",
|
||||
"owner": "Inhaber",
|
||||
"pending": "Ausstehendes Mitglied",
|
||||
"usual": "Einfaches Mitglied"
|
||||
},
|
||||
"save": "Neue Gruppe anlegen",
|
||||
"type": "Gruppentyp",
|
||||
"types": {
|
||||
"closed": "Geschlossene Gruppe",
|
||||
"hidden": "Versteckte Gruppe",
|
||||
"public": "Öffentliche Gruppe"
|
||||
},
|
||||
"update": "Änderung speichern"
|
||||
},
|
||||
"hashtags-filter": {
|
||||
@ -538,8 +572,6 @@
|
||||
"follow": "Folgen",
|
||||
"followers": "Folgen",
|
||||
"following": "Folge Ich",
|
||||
"groupGoal": "Ziel:",
|
||||
"groupSince": "Gründung",
|
||||
"invites": {
|
||||
"description": "Zur Einladung die E-Mail-Adresse hier eintragen.",
|
||||
"emailPlaceholder": "E-Mail-Adresse für die Einladung",
|
||||
|
||||
@ -375,10 +375,44 @@
|
||||
"following": "Following"
|
||||
},
|
||||
"group": {
|
||||
"actionRadii": {
|
||||
"continental": "Continental Group",
|
||||
"global": "Global Group",
|
||||
"interplanetary": "Interplanetary Group",
|
||||
"national": "National Group",
|
||||
"regional": "Regional Group"
|
||||
},
|
||||
"actionRadius": "Action radius",
|
||||
"foundation": "Foundation",
|
||||
"goal": "Goal of group",
|
||||
"group-created": "The group was created!",
|
||||
"group-updated": "The group data has been changed.",
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": "I'm a member",
|
||||
"join": "Join"
|
||||
},
|
||||
"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",
|
||||
"membersListTitle": "Group Members",
|
||||
"newGroup": "Create a new Group",
|
||||
"role": "Your role in the group",
|
||||
"roles": {
|
||||
"admin": "Administrator",
|
||||
"owner": "Owner",
|
||||
"pending": "Pending Member",
|
||||
"usual": "Simple Member"
|
||||
},
|
||||
"save": "Create new group",
|
||||
"type": "Group type",
|
||||
"types": {
|
||||
"closed": "Closed Group",
|
||||
"hidden": "Hidden Group",
|
||||
"public": "Public Group"
|
||||
},
|
||||
"update": "Save change"
|
||||
},
|
||||
"hashtags-filter": {
|
||||
@ -538,8 +572,6 @@
|
||||
"follow": "Follow",
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"groupGoal": "Goal:",
|
||||
"groupSince": "Foundation",
|
||||
"invites": {
|
||||
"description": "Enter their e-mail address for invitation.",
|
||||
"emailPlaceholder": "E-mail to invite",
|
||||
|
||||
@ -297,6 +297,16 @@
|
||||
"follow": "Seguir",
|
||||
"following": "Siguiendo"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Borrar búsqueda",
|
||||
"hashtag-search": "Buscando a #{hashtag}",
|
||||
|
||||
@ -286,6 +286,16 @@
|
||||
"follow": "Suivre",
|
||||
"following": "Je suis les"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Réinitialiser la recherche",
|
||||
"hashtag-search": "Recherche de #{hashtag}",
|
||||
|
||||
@ -294,6 +294,16 @@
|
||||
"follow": null,
|
||||
"following": null
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": null,
|
||||
"hashtag-search": null,
|
||||
|
||||
@ -82,6 +82,16 @@
|
||||
"follow": "Volgen",
|
||||
"following": "Volgt"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"login": {
|
||||
"email": "Uw E-mail",
|
||||
"hello": "Hallo",
|
||||
|
||||
@ -166,6 +166,16 @@
|
||||
"follow": "naśladować",
|
||||
"following": "w skutek"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"title": "Twoja bańka filtrująca"
|
||||
},
|
||||
|
||||
@ -332,6 +332,16 @@
|
||||
"follow": "Seguir",
|
||||
"following": "Seguindo"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Limpar pesquisa",
|
||||
"hashtag-search": "Procurando por #{hashtag}",
|
||||
|
||||
@ -311,6 +311,16 @@
|
||||
"follow": "Подписаться",
|
||||
"following": "Вы подписаны"
|
||||
},
|
||||
"group": {
|
||||
"foundation": null,
|
||||
"goal": null,
|
||||
"joinLeaveButton": {
|
||||
"iAmMember": null,
|
||||
"join": null
|
||||
},
|
||||
"membersCount": null,
|
||||
"membersListTitle": null
|
||||
},
|
||||
"hashtags-filter": {
|
||||
"clearSearch": "Очистить поиск",
|
||||
"hashtag-search": "Поиск по #{hashtag}",
|
||||
|
||||
@ -42,50 +42,98 @@
|
||||
{{ user.location.name }}
|
||||
</ds-text> -->
|
||||
<ds-text align="center" color="soft" size="small">
|
||||
{{ $t('profile.groupSince') }} {{ group.createdAt | date('MMMM yyyy') }}
|
||||
{{ $t('group.foundation') }} {{ group.createdAt | date('MMMM yyyy') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<!-- <client-only>
|
||||
<client-only>
|
||||
<ds-number :label="$t('group.membersCount')">
|
||||
<count-to
|
||||
slot="count"
|
||||
:start-val="membersCountStartValue"
|
||||
:end-val="groupMembers.length"
|
||||
/>
|
||||
</ds-number>
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<!-- <ds-flex-item>
|
||||
<client-only>
|
||||
<ds-number :label="$t('profile.followers')">
|
||||
<hc-count-to
|
||||
<count-to
|
||||
slot="count"
|
||||
:start-val="followedByCountStartValue"
|
||||
:end-val="user.followedByCount"
|
||||
/>
|
||||
</ds-number>
|
||||
</client-only> -->
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<!-- <client-only>
|
||||
</client-only>
|
||||
</ds-flex-item> -->
|
||||
<!-- <ds-flex-item>
|
||||
<client-only>
|
||||
<ds-number :label="$t('profile.following')">
|
||||
<hc-count-to slot="count" :end-val="user.followingCount" />
|
||||
<count-to slot="count" :end-val="user.followingCount" />
|
||||
</ds-number>
|
||||
</client-only> -->
|
||||
</ds-flex-item>
|
||||
</client-only>
|
||||
</ds-flex-item> -->
|
||||
</ds-flex>
|
||||
<div v-if="!isGroupMember" class="action-buttons">
|
||||
<div class="action-buttons">
|
||||
<!-- <base-button v-if="user.isBlocked" @click="unblockUser(user)">
|
||||
{{ $t('settings.blocked-users.unblock') }}
|
||||
</base-button>
|
||||
<base-button v-if="user.isMuted" @click="unmuteUser(user)">
|
||||
{{ $t('settings.muted-users.unmute') }}
|
||||
</base-button>
|
||||
<hc-follow-button
|
||||
<follow-button
|
||||
v-if="!user.isMuted && !user.isBlocked"
|
||||
:follow-id="user.id"
|
||||
:is-followed="user.followedByCurrentUser"
|
||||
@optimistic="optimisticFollow"
|
||||
@update="updateFollow"
|
||||
/> -->
|
||||
<join-leave-button
|
||||
:group="group || {}"
|
||||
:userId="currentUser.id"
|
||||
:isMember="isGroupMember"
|
||||
:disabled="isGroupOwner"
|
||||
:loading="$apollo.loading"
|
||||
@prepare="prepareJoinLeave"
|
||||
@update="updateJoinLeave"
|
||||
/>
|
||||
<!-- implement:
|
||||
v-if="!user.isMuted && !user.isBlocked" -->
|
||||
</div>
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</ds-space>
|
||||
<template v-if="group.about">
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<ds-text color="soft" size="small" class="hyphenate-text">
|
||||
{{ $t('profile.groupGoal') }} {{ group.about }}
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
{{ $t('group.goal') }}
|
||||
</ds-text>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip>{{ group.about }}</ds-chip>
|
||||
</div>
|
||||
</ds-space>
|
||||
</template>
|
||||
</base-card>
|
||||
@ -93,7 +141,17 @@
|
||||
<ds-heading tag="h3" soft style="text-align: center; margin-bottom: 10px">
|
||||
{{ $t('profile.network.title') }}
|
||||
</ds-heading>
|
||||
<!-- <follow-list
|
||||
<!-- Group members list -->
|
||||
<profile-list
|
||||
:uniqueName="`groupMembersFilter`"
|
||||
:title="$t('group.membersListTitle')"
|
||||
:allProfilesCount="groupMembers.length"
|
||||
:profiles="groupMembers"
|
||||
:loading="$apollo.loading"
|
||||
@fetchAllProfiles="fetchAllMembers"
|
||||
/>
|
||||
<!-- <ds-space />
|
||||
<follow-list
|
||||
:loading="$apollo.loading"
|
||||
:user="user"
|
||||
type="followedBy"
|
||||
@ -159,7 +217,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<ds-grid-item column-span="fullWidth">
|
||||
<hc-empty margin="xx-large" icon="file" />
|
||||
<empty margin="xx-large" icon="file" />
|
||||
</ds-grid-item>
|
||||
</template>
|
||||
</masonry-grid>
|
||||
@ -173,26 +231,26 @@
|
||||
|
||||
<script>
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import postListActions from '~/mixins/postListActions'
|
||||
import PostTeaser from '~/components/PostTeaser/PostTeaser.vue'
|
||||
// import HcFollowButton from '~/components/FollowButton.vue'
|
||||
// import HcCountTo from '~/components/CountTo.vue'
|
||||
// import HcBadges from '~/components/Badges.vue'
|
||||
// import FollowList from '~/components/features/FollowList/FollowList'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
// import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import AvatarUploader from '~/components/Uploader/AvatarUploader'
|
||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
// import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation'
|
||||
// import { profilePagePosts } from '~/graphql/PostQuery'
|
||||
import { groupQuery } from '~/graphql/groups'
|
||||
import { updateGroupMutation } from '~/graphql/groups.js'
|
||||
import { updateGroupMutation, groupQuery, groupMembersQuery } from '~/graphql/groups'
|
||||
// import { muteUser, unmuteUser } from '~/graphql/settings/MutedUsers'
|
||||
// import { blockUser, unblockUser } from '~/graphql/settings/BlockedUsers'
|
||||
// import UpdateQuery from '~/components/utils/UpdateQuery'
|
||||
import postListActions from '~/mixins/postListActions'
|
||||
import AvatarUploader from '~/components/Uploader/AvatarUploader'
|
||||
// import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import CountTo from '~/components/CountTo.vue'
|
||||
import Empty from '~/components/Empty/Empty'
|
||||
// import FollowButton from '~/components/Button/FollowButton'
|
||||
// import FollowList from '~/components/features/ProfileList/FollowList'
|
||||
import JoinLeaveButton from '~/components/Button/JoinLeaveButton'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
import PostTeaser from '~/components/PostTeaser/PostTeaser.vue'
|
||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||
import ProfileList from '~/components/features/ProfileList/ProfileList'
|
||||
// import SocialMedia from '~/components/SocialMedia/SocialMedia'
|
||||
// import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation'
|
||||
|
||||
// const tabToFilterMapping = ({ tab, id }) => {
|
||||
// return {
|
||||
@ -204,18 +262,19 @@ import { updateGroupMutation } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
// SocialMedia,
|
||||
PostTeaser,
|
||||
// HcFollowButton,
|
||||
// HcCountTo,
|
||||
// HcBadges,
|
||||
HcEmpty,
|
||||
ProfileAvatar,
|
||||
// ContentMenu,
|
||||
AvatarUploader,
|
||||
// ContentMenu,
|
||||
CountTo,
|
||||
Empty,
|
||||
// FollowButton,
|
||||
// FollowList,
|
||||
JoinLeaveButton,
|
||||
PostTeaser,
|
||||
ProfileAvatar,
|
||||
ProfileList,
|
||||
MasonryGrid,
|
||||
MasonryGridItem,
|
||||
// FollowList,
|
||||
// SocialMedia,
|
||||
// TabNavigation,
|
||||
},
|
||||
mixins: [postListActions],
|
||||
@ -223,31 +282,45 @@ export default {
|
||||
name: 'slide-up',
|
||||
mode: 'out-in',
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.groupName,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
|
||||
return {
|
||||
Group: [],
|
||||
GroupMembers: [],
|
||||
posts: [],
|
||||
hasMore: true,
|
||||
offset: 0,
|
||||
pageSize: 6,
|
||||
tabActive: 'post',
|
||||
// hasMore: true,
|
||||
// offset: 0,
|
||||
// pageSize: 6,
|
||||
// tabActive: 'post',
|
||||
// filter,
|
||||
followedByCountStartValue: 0,
|
||||
followedByCount: 7,
|
||||
followingCount: 7,
|
||||
// followedByCountStartValue: 0,
|
||||
// followedByCount: 7,
|
||||
// followingCount: 7,
|
||||
membersCountStartValue: 0,
|
||||
membersCountToLoad: Infinity,
|
||||
updateGroupMutation,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isGroupOwner() {
|
||||
return this.group.myRole === 'owner'
|
||||
},
|
||||
isGroupMember() {
|
||||
return this.group.myRole
|
||||
currentUser() {
|
||||
return this.$store.getters['auth/user']
|
||||
},
|
||||
group() {
|
||||
return this.Group ? this.Group[0] : {}
|
||||
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
|
||||
},
|
||||
groupName() {
|
||||
const { name } = this.group || {}
|
||||
@ -384,10 +457,17 @@ export default {
|
||||
// this.user.followedByCurrentUser = followedByCurrentUser
|
||||
// this.user.followedBy = followedBy
|
||||
// },
|
||||
// fetchAllConnections(type) {
|
||||
// if (type === 'following') this.followingCount = Infinity
|
||||
// if (type === 'followedBy') this.followedByCount = Infinity
|
||||
// },
|
||||
prepareJoinLeave() {
|
||||
// "membersCountStartValue" is updated to avoid counting from 0 when join/leave
|
||||
this.membersCountStartValue = this.GroupMembers.length
|
||||
},
|
||||
updateJoinLeave({ myRoleInGroup }) {
|
||||
this.Group[0].myRole = myRoleInGroup
|
||||
this.$apollo.queries.GroupMembers.refetch()
|
||||
},
|
||||
fetchAllMembers() {
|
||||
this.membersCountToLoad = Infinity
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
// profilePagePosts: {
|
||||
@ -421,6 +501,17 @@ export default {
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
GroupMembers: {
|
||||
query() {
|
||||
return groupMembersQuery
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
id: this.$route.params.id,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -449,4 +540,11 @@ export default {
|
||||
margin-bottom: $space-x-small;
|
||||
}
|
||||
}
|
||||
.centered-text {
|
||||
text-align: center;
|
||||
margin-bottom: $space-xxx-small;
|
||||
}
|
||||
.chip {
|
||||
margin-bottom: $space-x-small;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -172,10 +172,10 @@
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import postListActions from '~/mixins/postListActions'
|
||||
import PostTeaser from '~/components/PostTeaser/PostTeaser.vue'
|
||||
import HcFollowButton from '~/components/FollowButton.vue'
|
||||
import HcFollowButton from '~/components/Button/FollowButton'
|
||||
import HcCountTo from '~/components/CountTo.vue'
|
||||
import HcBadges from '~/components/Badges.vue'
|
||||
import FollowList from '~/components/features/FollowList/FollowList'
|
||||
import FollowList, { followListVisibleCount } from '~/components/features/ProfileList/FollowList'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import AvatarUploader from '~/components/Uploader/AvatarUploader'
|
||||
@ -220,6 +220,11 @@ export default {
|
||||
name: 'slide-up',
|
||||
mode: 'out-in',
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.userName,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const filter = tabToFilterMapping({ tab: 'post', id: this.$route.params.id })
|
||||
return {
|
||||
@ -231,8 +236,8 @@ export default {
|
||||
tabActive: 'post',
|
||||
filter,
|
||||
followedByCountStartValue: 0,
|
||||
followedByCount: 7,
|
||||
followingCount: 7,
|
||||
followedByCount: followListVisibleCount,
|
||||
followingCount: followListVisibleCount,
|
||||
updateUserMutation: updateUserMutation(),
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user