diff --git a/components/messages.js b/components/messages.js index c9ce76b9..d0e929f9 100644 --- a/components/messages.js +++ b/components/messages.js @@ -65,6 +65,7 @@ export const labels = defineMessages({ singleDay: { id: 'label.single-day', defaultMessage: 'Single day' }, dateRange: { id: 'label.date-range', defaultMessage: 'Date range' }, viewDetails: { id: 'label.view-details', defaultMessage: 'View details' }, + deleteTeam: { id: 'label.delete-team', defaultMessage: 'Delete team' }, }); export const messages = defineMessages({ @@ -132,6 +133,10 @@ export const messages = defineMessages({ id: 'message.team-not-found', defaultMessage: 'Team not found.', }, + deleteTeam: { + id: 'message.delete-team', + defaultMessage: 'To delete this team, type {confirmation} in the box below to confirm.', + }, }); export const devices = defineMessages({ diff --git a/components/pages/settings/teams/TeamDeleteForm.js b/components/pages/settings/teams/TeamDeleteForm.js new file mode 100644 index 00000000..20e76a6b --- /dev/null +++ b/components/pages/settings/teams/TeamDeleteForm.js @@ -0,0 +1,44 @@ +import { + Button, + Form, + FormRow, + FormButtons, + FormInput, + SubmitButton, + TextField, +} from 'react-basics'; +import { useIntl } from 'react-intl'; +import { labels, messages } from 'components/messages'; +import useApi from 'hooks/useApi'; + +const CONFIRM_VALUE = 'DELETE'; + +export default function TeamDeleteForm({ teamId, onSave, onClose }) { + const { formatMessage } = useIntl(); + const { del, useMutation } = useApi(); + const { mutate, error } = useMutation(data => del(`/teams/${teamId}`, data)); + + const handleSubmit = async data => { + mutate(data, { + onSuccess: async () => { + onSave(); + onClose(); + }, + }); + }; + + return ( +
+

{formatMessage(messages.deleteTeam, { confirmation: CONFIRM_VALUE })}

+ + value === CONFIRM_VALUE }}> + + + + + {formatMessage(labels.delete)} + + +
+ ); +} diff --git a/components/pages/settings/teams/TeamEditForm.js b/components/pages/settings/teams/TeamEditForm.js index ca0e7f2d..ba5b21e2 100644 --- a/components/pages/settings/teams/TeamEditForm.js +++ b/components/pages/settings/teams/TeamEditForm.js @@ -16,7 +16,7 @@ import { labels } from 'components/messages'; const generateId = () => getRandomChars(16); -export default function TeamEditForm({ teamId, data, onSave }) { +export default function TeamEditForm({ teamId, data, onSave, readOnly }) { const { formatMessage } = useIntl(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data)); @@ -47,19 +47,26 @@ export default function TeamEditForm({ teamId, data, onSave }) { - - - + {!readOnly && ( + + + + )} + {readOnly && data.name} - + {!readOnly && ( + + )} - - {formatMessage(labels.save)} - + {!readOnly && ( + + {formatMessage(labels.save)} + + )} ); } diff --git a/components/pages/settings/teams/TeamMembers.js b/components/pages/settings/teams/TeamMembers.js index c9901e34..73926385 100644 --- a/components/pages/settings/teams/TeamMembers.js +++ b/components/pages/settings/teams/TeamMembers.js @@ -2,7 +2,7 @@ import { Loading } from 'react-basics'; import useApi from 'hooks/useApi'; import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable'; -export default function TeamMembers({ teamId }) { +export default function TeamMembers({ teamId, readOnly }) { const { get, useQuery } = useApi(); const { data, isLoading } = useQuery(['teams:users', teamId], () => get(`/teams/${teamId}/users`), @@ -12,5 +12,5 @@ export default function TeamMembers({ teamId }) { return ; } - return ; + return ; } diff --git a/components/pages/settings/teams/TeamMembersTable.js b/components/pages/settings/teams/TeamMembersTable.js index 036ca26c..7d66dfa6 100644 --- a/components/pages/settings/teams/TeamMembersTable.js +++ b/components/pages/settings/teams/TeamMembersTable.js @@ -16,7 +16,7 @@ import { ROLES } from 'lib/constants'; import { labels } from 'components/messages'; import useUser from 'hooks/useUser'; -export default function TeamMembersTable({ data = [] }) { +export default function TeamMembersTable({ data = [], readOnly }) { const { formatMessage } = useIntl(); const { user } = useUser(); @@ -44,9 +44,9 @@ export default function TeamMembersTable({ data = [] }) { role: formatMessage( labels[Object.keys(ROLES).find(key => ROLES[key] === row.role) || labels.unknown], ), - action: ( + action: !readOnly && ( - + + + + {close => } + + ), }; diff --git a/lib/auth.ts b/lib/auth.ts index 1113ebf5..31b34fe9 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,4 +1,5 @@ import debug from 'debug'; +import { validate } from 'uuid'; import cache from 'lib/cache'; import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants'; import { secret } from 'lib/crypto'; @@ -60,10 +61,6 @@ export async function canViewWebsite({ user }: Auth, websiteId: string) { return user.id === website.userId; } - if (website.teamId) { - return getTeamUser(website.teamId, user.id); - } - return false; } @@ -86,18 +83,16 @@ export async function canUpdateWebsite({ user }: Auth, websiteId: string) { return true; } + if (!validate(websiteId)) { + return false; + } + const website = await cache.fetchWebsite(websiteId); if (website.userId) { return user.id === website.userId; } - if (website.teamId) { - const teamUser = await getTeamUser(website.teamId, user.id); - - return hasPermission(teamUser.role, PERMISSIONS.websiteUpdate); - } - return false; } @@ -112,12 +107,6 @@ export async function canDeleteWebsite({ user }: Auth, websiteId: string) { return user.id === website.userId; } - if (website.teamId) { - const teamUser = await getTeamUser(website.teamId, user.id); - - return hasPermission(teamUser.role, PERMISSIONS.websiteDelete); - } - return false; } @@ -144,9 +133,13 @@ export async function canUpdateTeam({ user }: Auth, teamId: string) { return true; } - const teamUser = await getTeamUser(teamId, user.id); + if (validate(teamId)) { + const teamUser = await getTeamUser(teamId, user.id); - return hasPermission(teamUser.role, PERMISSIONS.teamUpdate); + return hasPermission(teamUser.role, PERMISSIONS.teamUpdate); + } + + return false; } export async function canDeleteTeam({ user }: Auth, teamId: string) { @@ -154,9 +147,13 @@ export async function canDeleteTeam({ user }: Auth, teamId: string) { return true; } - const teamUser = await getTeamUser(teamId, user.id); + if (validate(teamId)) { + const teamUser = await getTeamUser(teamId, user.id); - return hasPermission(teamUser.role, PERMISSIONS.teamDelete); + return hasPermission(teamUser.role, PERMISSIONS.teamDelete); + } + + return false; } export async function canCreateUser({ user }: Auth) { diff --git a/lib/constants.ts b/lib/constants.ts index 4819eb53..57746404 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -33,7 +33,6 @@ export const ROLES = { user: 'user', teamOwner: 'team-owner', teamMember: 'team-member', - teamGuest: 'team-guest', } as const; export const PERMISSIONS = { @@ -54,19 +53,8 @@ export const ROLE_PERMISSIONS = { PERMISSIONS.websiteDelete, PERMISSIONS.teamCreate, ], - [ROLES.teamOwner]: [ - PERMISSIONS.teamUpdate, - PERMISSIONS.teamDelete, - PERMISSIONS.websiteCreate, - PERMISSIONS.websiteUpdate, - PERMISSIONS.websiteDelete, - ], - [ROLES.teamMember]: [ - PERMISSIONS.websiteCreate, - PERMISSIONS.websiteUpdate, - PERMISSIONS.websiteDelete, - ], - [ROLES.teamGuest]: [], + [ROLES.teamOwner]: [PERMISSIONS.teamUpdate, PERMISSIONS.teamDelete], + [ROLES.teamMember]: [], } as const; export const THEME_COLORS = { diff --git a/queries/admin/team.ts b/queries/admin/team.ts index 7d9d7e4e..238aaab3 100644 --- a/queries/admin/team.ts +++ b/queries/admin/team.ts @@ -6,6 +6,9 @@ import { ROLES } from 'lib/constants'; export async function getTeam(where: Prisma.TeamWhereInput): Promise { return prisma.client.team.findFirst({ where, + include: { + teamUser: true, + }, }); }