refactor: migrate to common eslint+prettier configs (#4168)

* refactor: migrate to common eslint+prettier configs

* fix: prettier frontend

* feat: config changes

* fix: lint issues

* fix: lint

* fix: type imports

* fix: cyclical import issue

* fix: lockfile

* fix: missing dep

* fix: switch to tabs

* fix: continue switch to tabs

* fix: rustfmt parity

* fix: moderation lint issue

* fix: lint issues

* fix: ui intl

* fix: lint issues

* Revert "fix: rustfmt parity"

This reverts commit cb99d2376c321d813d4b7fc7e2a213bb30a54711.

* feat: revert last rs
This commit is contained in:
Cal H.
2025-08-14 21:48:38 +01:00
committed by GitHub
parent 82697278dc
commit 2aabcf36ee
702 changed files with 101360 additions and 102020 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,28 @@
<template>
<div class="normal-page__content">
<div class="universal-card">
<h2>Analytics</h2>
<div class="normal-page__content">
<div class="universal-card">
<h2>Analytics</h2>
<p>
This page shows you the analytics for your organization's projects. You can see the number
of downloads, page views and revenue earned for all of your projects, as well as the total
downloads and page views for each project by country.
</p>
</div>
<p>
This page shows you the analytics for your organization's projects. You can see the number
of downloads, page views and revenue earned for all of your projects, as well as the total
downloads and page views for each project by country.
</p>
</div>
<ChartDisplay :projects="projects.map((x) => ({ title: x.name, ...x }))" />
</div>
<ChartDisplay :projects="projects.map((x) => ({ title: x.name, ...x }))" />
</div>
</template>
<script setup>
import ChartDisplay from "~/components/ui/charts/ChartDisplay.vue";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
import { injectOrganizationContext } from '~/providers/organization-context.ts'
const { projects } = injectOrganizationContext();
const { projects } = injectOrganizationContext()
</script>
<style scoped lang="scss">
.markdown-body {
margin-bottom: var(--gap-md);
margin-bottom: var(--gap-md);
}
</style>

View File

@@ -1,219 +1,220 @@
<script setup>
import { SaveIcon, TrashIcon, UploadIcon } from "@modrinth/assets";
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from "@modrinth/ui";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
import { SaveIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from '@modrinth/ui'
const { addNotification } = injectNotificationManager();
import { injectOrganizationContext } from '~/providers/organization-context.ts'
const { addNotification } = injectNotificationManager()
const {
organization,
refresh: refreshOrganization,
hasPermission,
deleteIcon,
patchIcon,
patchOrganization,
} = injectOrganizationContext();
organization,
refresh: refreshOrganization,
hasPermission,
deleteIcon,
patchIcon,
patchOrganization,
} = injectOrganizationContext()
const icon = ref(null);
const deletedIcon = ref(false);
const previewImage = ref(null);
const icon = ref(null)
const deletedIcon = ref(false)
const previewImage = ref(null)
const name = ref(organization.value.name);
const slug = ref(organization.value.slug);
const name = ref(organization.value.name)
const slug = ref(organization.value.slug)
const summary = ref(organization.value.description);
const summary = ref(organization.value.description)
const patchData = computed(() => {
const data = {};
if (name.value !== organization.value.name) {
data.name = name.value;
}
if (slug.value !== organization.value.slug) {
data.slug = slug.value;
}
if (summary.value !== organization.value.description) {
data.description = summary.value;
}
return data;
});
const data = {}
if (name.value !== organization.value.name) {
data.name = name.value
}
if (slug.value !== organization.value.slug) {
data.slug = slug.value
}
if (summary.value !== organization.value.description) {
data.description = summary.value
}
return data
})
const hasChanges = computed(() => {
return Object.keys(patchData.value).length > 0 || deletedIcon.value || icon.value;
});
return Object.keys(patchData.value).length > 0 || deletedIcon.value || icon.value
})
const markIconForDeletion = () => {
deletedIcon.value = true;
icon.value = null;
previewImage.value = null;
};
deletedIcon.value = true
icon.value = null
previewImage.value = null
}
const showPreviewImage = (files) => {
const reader = new FileReader();
const reader = new FileReader()
icon.value = files[0];
deletedIcon.value = false;
icon.value = files[0]
deletedIcon.value = false
reader.readAsDataURL(icon.value);
reader.onload = (event) => {
previewImage.value = event.target.result;
};
};
reader.readAsDataURL(icon.value)
reader.onload = (event) => {
previewImage.value = event.target.result
}
}
const orgId = useRouteId();
const orgId = useRouteId()
const onSaveChanges = useClientTry(async () => {
if (hasChanges.value) {
await patchOrganization(orgId, patchData.value);
}
if (hasChanges.value) {
await patchOrganization(orgId, patchData.value)
}
if (deletedIcon.value) {
await deleteIcon();
deletedIcon.value = false;
} else if (icon.value) {
await patchIcon(icon.value);
icon.value = null;
}
if (deletedIcon.value) {
await deleteIcon()
deletedIcon.value = false
} else if (icon.value) {
await patchIcon(icon.value)
icon.value = null
}
await refreshOrganization();
await refreshOrganization()
addNotification({
title: "Organization updated",
text: "Your organization has been updated.",
type: "success",
});
});
addNotification({
title: 'Organization updated',
text: 'Your organization has been updated.',
type: 'success',
})
})
const onDeleteOrganization = useClientTry(async () => {
await useBaseFetch(`organization/${orgId}`, {
method: "DELETE",
apiVersion: 3,
});
await useBaseFetch(`organization/${orgId}`, {
method: 'DELETE',
apiVersion: 3,
})
addNotification({
title: "Organization deleted",
text: "Your organization has been deleted.",
type: "success",
});
addNotification({
title: 'Organization deleted',
text: 'Your organization has been deleted.',
type: 'success',
})
await navigateTo("/dashboard/organizations");
});
await navigateTo('/dashboard/organizations')
})
</script>
<template>
<div class="normal-page__content">
<ConfirmModal
ref="modal_deletion"
:title="`Are you sure you want to delete ${organization.name}?`"
description="This will delete this organization forever (like *forever* ever)."
:has-to-type="true"
proceed-label="Delete"
:confirmation-text="organization.name"
@proceed="onDeleteOrganization"
/>
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Organization information</span>
</h3>
</div>
<label for="project-icon">
<span class="label__title">Icon</span>
</label>
<div class="input-group">
<Avatar
:src="deletedIcon ? null : previewImage ? previewImage : organization.icon_url"
:alt="organization.name"
size="md"
class="project__icon"
/>
<div class="input-stack">
<FileInput
id="project-icon"
:max-size="262144"
:show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp"
class="btn"
prompt="Upload icon"
:disabled="!hasPermission"
@change="showPreviewImage"
>
<UploadIcon />
</FileInput>
<Button
v-if="!deletedIcon && (previewImage || organization.icon_url)"
:disabled="!hasPermission"
@click="markIconForDeletion"
>
<TrashIcon />
Remove icon
</Button>
</div>
</div>
<div class="normal-page__content">
<ConfirmModal
ref="modal_deletion"
:title="`Are you sure you want to delete ${organization.name}?`"
description="This will delete this organization forever (like *forever* ever)."
:has-to-type="true"
proceed-label="Delete"
:confirmation-text="organization.name"
@proceed="onDeleteOrganization"
/>
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Organization information</span>
</h3>
</div>
<label for="project-icon">
<span class="label__title">Icon</span>
</label>
<div class="input-group">
<Avatar
:src="deletedIcon ? null : previewImage ? previewImage : organization.icon_url"
:alt="organization.name"
size="md"
class="project__icon"
/>
<div class="input-stack">
<FileInput
id="project-icon"
:max-size="262144"
:show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp"
class="btn"
prompt="Upload icon"
:disabled="!hasPermission"
@change="showPreviewImage"
>
<UploadIcon />
</FileInput>
<Button
v-if="!deletedIcon && (previewImage || organization.icon_url)"
:disabled="!hasPermission"
@click="markIconForDeletion"
>
<TrashIcon />
Remove icon
</Button>
</div>
</div>
<label for="project-name">
<span class="label__title">Name</span>
</label>
<input
id="project-name"
v-model="name"
maxlength="2048"
type="text"
:disabled="!hasPermission"
/>
<label for="project-name">
<span class="label__title">Name</span>
</label>
<input
id="project-name"
v-model="name"
maxlength="2048"
type="text"
:disabled="!hasPermission"
/>
<label for="project-slug">
<span class="label__title">URL</span>
</label>
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/organization/</div>
<input
id="project-slug"
v-model="slug"
type="text"
maxlength="64"
autocomplete="off"
:disabled="!hasPermission"
/>
</div>
<label for="project-slug">
<span class="label__title">URL</span>
</label>
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/organization/</div>
<input
id="project-slug"
v-model="slug"
type="text"
maxlength="64"
autocomplete="off"
:disabled="!hasPermission"
/>
</div>
<label for="project-summary">
<span class="label__title">Summary</span>
</label>
<div class="textarea-wrapper summary-input">
<textarea
id="project-summary"
v-model="summary"
maxlength="256"
:disabled="!hasPermission"
/>
</div>
<div class="button-group">
<Button color="primary" :disabled="!hasChanges" @click="onSaveChanges">
<SaveIcon />
Save changes
</Button>
</div>
</div>
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Delete organization</span>
</h3>
</div>
<p>
Deleting your organization will transfer all of its projects to the organization owner. This
action cannot be undone.
</p>
<Button color="danger" @click="() => $refs.modal_deletion.show()">
<TrashIcon />
Delete organization
</Button>
</div>
</div>
<label for="project-summary">
<span class="label__title">Summary</span>
</label>
<div class="textarea-wrapper summary-input">
<textarea
id="project-summary"
v-model="summary"
maxlength="256"
:disabled="!hasPermission"
/>
</div>
<div class="button-group">
<Button color="primary" :disabled="!hasChanges" @click="onSaveChanges">
<SaveIcon />
Save changes
</Button>
</div>
</div>
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Delete organization</span>
</h3>
</div>
<p>
Deleting your organization will transfer all of its projects to the organization owner. This
action cannot be undone.
</p>
<Button color="danger" @click="() => $refs.modal_deletion.show()">
<TrashIcon />
Delete organization
</Button>
</div>
</div>
</template>
<style scoped lang="scss">
.summary-input {
min-height: 8rem;
max-width: 24rem;
min-height: 8rem;
max-width: 24rem;
}
</style>

View File

@@ -1,439 +1,440 @@
<template>
<div class="normal-page__content">
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Manage members</span>
</h3>
</div>
<span class="label">
<span class="label__title">Invite a member</span>
<span class="label__description">
Enter the Modrinth username of the person you'd like to invite to be a member of this
organization.
</span>
</span>
<div class="input-group">
<input
id="username"
v-model="currentUsername"
type="text"
placeholder="Username"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES,
)
"
@keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)"
/>
<label for="username" class="hidden">Username</label>
<Button
color="primary"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES,
)
"
@click="() => onInviteTeamMember(organization.team_id, currentUsername)"
>
<UserPlusIcon />
Invite
</Button>
</div>
<div class="adjacent-input">
<span class="label">
<span class="label__title">Leave organization</span>
<span class="label__description">
Remove yourself as a member of this organization.
</span>
</span>
<Button
color="danger"
:disabled="currentMember.is_owner"
@click="() => onLeaveProject(organization.team_id, auth.user.id)"
>
<UserRemoveIcon />
Leave organization
</Button>
</div>
</div>
<div
v-for="(member, index) in allTeamMembers"
:key="member.user.id"
class="member universal-card"
:class="{ open: openTeamMembers.includes(member.user.id) }"
>
<div class="member-header">
<div class="info">
<Avatar :src="member.user.avatar_url" :alt="member.user.username" size="sm" circle />
<div class="text">
<nuxt-link :to="'/user/' + member.user.username" class="name">
<p>{{ member.user.username }}</p>
<CrownIcon v-if="member.is_owner" v-tooltip="'Organization owner'" />
</nuxt-link>
<p>{{ member.role }}</p>
</div>
</div>
<div class="side-buttons">
<Badge v-if="member.accepted" type="accepted" />
<Badge v-else type="pending" />
<Button
icon-only
transparent
class="dropdown-icon"
@click="
openTeamMembers.indexOf(member.user.id) === -1
? openTeamMembers.push(member.user.id)
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
"
>
<DropdownIcon />
</Button>
</div>
</div>
<div class="content">
<div class="adjacent-input">
<label :for="`member-${member.user.id}-role`">
<span class="label__title">Role</span>
<span class="label__description">
The title of the role that this member plays for this organization.
</span>
</label>
<input
:id="`member-${member.user.id}-role`"
v-model="member.role"
type="text"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
/>
</div>
<div class="adjacent-input">
<label :for="`member-${member.user.id}-monetization-weight`">
<span class="label__title">Monetization weight</span>
<span class="label__description">
Relative to all other members' monetization weights, this determines what portion of
the organization projects' revenue goes to this member.
</span>
</label>
<input
:id="`member-${member.user.id}-monetization-weight`"
v-model="member.payouts_split"
type="number"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
/>
</div>
<template v-if="!member.is_owner">
<span class="label">
<span class="label__title">Project permissions</span>
</span>
<div class="permissions">
<Checkbox
v-for="[label, permission] in Object.entries(projectPermissions)"
:key="permission"
:model-value="isPermission(member.permissions, permission)"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER_DEFAULT_PERMISSIONS,
) || !isPermission(currentMember.permissions, permission)
"
:label="permToLabel(label)"
@update:model-value="allTeamMembers[index].permissions ^= permission"
/>
</div>
</template>
<template v-if="!member.is_owner">
<span class="label">
<span class="label__title">Organization permissions</span>
</span>
<div class="permissions">
<Checkbox
v-for="[label, permission] in Object.entries(organizationPermissions)"
:key="permission"
:model-value="isPermission(member.organization_permissions, permission)"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
) || !isPermission(currentMember.organization_permissions, permission)
"
:label="permToLabel(label)"
@update:model-value="allTeamMembers[index].organization_permissions ^= permission"
/>
</div>
</template>
<div class="input-group">
<Button
color="primary"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
@click="onUpdateTeamMember(organization.team_id, member)"
>
<SaveIcon />
Save changes
</Button>
<Button
v-if="!member.is_owner"
color="danger"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
) &&
!isPermission(
currentMember.organization_permissions,
organizationPermissions.REMOVE_MEMBER,
)
"
@click="onRemoveMember(organization.team_id, member)"
>
<UserRemoveIcon />
Remove member
</Button>
<Button
v-if="!member.is_owner && currentMember.is_owner && member.accepted"
@click="() => onTransferOwnership(organization.team_id, member.user.id)"
>
<TransferIcon />
Transfer ownership
</Button>
</div>
</div>
</div>
</div>
<div class="normal-page__content">
<div class="universal-card">
<div class="label">
<h3>
<span class="label__title size-card-header">Manage members</span>
</h3>
</div>
<span class="label">
<span class="label__title">Invite a member</span>
<span class="label__description">
Enter the Modrinth username of the person you'd like to invite to be a member of this
organization.
</span>
</span>
<div class="input-group">
<input
id="username"
v-model="currentUsername"
type="text"
placeholder="Username"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES,
)
"
@keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)"
/>
<label for="username" class="hidden">Username</label>
<Button
color="primary"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.MANAGE_INVITES,
)
"
@click="() => onInviteTeamMember(organization.team_id, currentUsername)"
>
<UserPlusIcon />
Invite
</Button>
</div>
<div class="adjacent-input">
<span class="label">
<span class="label__title">Leave organization</span>
<span class="label__description">
Remove yourself as a member of this organization.
</span>
</span>
<Button
color="danger"
:disabled="currentMember.is_owner"
@click="() => onLeaveProject(organization.team_id, auth.user.id)"
>
<UserRemoveIcon />
Leave organization
</Button>
</div>
</div>
<div
v-for="(member, index) in allTeamMembers"
:key="member.user.id"
class="member universal-card"
:class="{ open: openTeamMembers.includes(member.user.id) }"
>
<div class="member-header">
<div class="info">
<Avatar :src="member.user.avatar_url" :alt="member.user.username" size="sm" circle />
<div class="text">
<nuxt-link :to="'/user/' + member.user.username" class="name">
<p>{{ member.user.username }}</p>
<CrownIcon v-if="member.is_owner" v-tooltip="'Organization owner'" />
</nuxt-link>
<p>{{ member.role }}</p>
</div>
</div>
<div class="side-buttons">
<Badge v-if="member.accepted" type="accepted" />
<Badge v-else type="pending" />
<Button
icon-only
transparent
class="dropdown-icon"
@click="
openTeamMembers.indexOf(member.user.id) === -1
? openTeamMembers.push(member.user.id)
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
"
>
<DropdownIcon />
</Button>
</div>
</div>
<div class="content">
<div class="adjacent-input">
<label :for="`member-${member.user.id}-role`">
<span class="label__title">Role</span>
<span class="label__description">
The title of the role that this member plays for this organization.
</span>
</label>
<input
:id="`member-${member.user.id}-role`"
v-model="member.role"
type="text"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
/>
</div>
<div class="adjacent-input">
<label :for="`member-${member.user.id}-monetization-weight`">
<span class="label__title">Monetization weight</span>
<span class="label__description">
Relative to all other members' monetization weights, this determines what portion of
the organization projects' revenue goes to this member.
</span>
</label>
<input
:id="`member-${member.user.id}-monetization-weight`"
v-model="member.payouts_split"
type="number"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
/>
</div>
<template v-if="!member.is_owner">
<span class="label">
<span class="label__title">Project permissions</span>
</span>
<div class="permissions">
<Checkbox
v-for="[label, permission] in Object.entries(projectPermissions)"
:key="permission"
:model-value="isPermission(member.permissions, permission)"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER_DEFAULT_PERMISSIONS,
) || !isPermission(currentMember.permissions, permission)
"
:label="permToLabel(label)"
@update:model-value="allTeamMembers[index].permissions ^= permission"
/>
</div>
</template>
<template v-if="!member.is_owner">
<span class="label">
<span class="label__title">Organization permissions</span>
</span>
<div class="permissions">
<Checkbox
v-for="[label, permission] in Object.entries(organizationPermissions)"
:key="permission"
:model-value="isPermission(member.organization_permissions, permission)"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
) || !isPermission(currentMember.organization_permissions, permission)
"
:label="permToLabel(label)"
@update:model-value="allTeamMembers[index].organization_permissions ^= permission"
/>
</div>
</template>
<div class="input-group">
<Button
color="primary"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
)
"
@click="onUpdateTeamMember(organization.team_id, member)"
>
<SaveIcon />
Save changes
</Button>
<Button
v-if="!member.is_owner"
color="danger"
:disabled="
!isPermission(
currentMember.organization_permissions,
organizationPermissions.EDIT_MEMBER,
) &&
!isPermission(
currentMember.organization_permissions,
organizationPermissions.REMOVE_MEMBER,
)
"
@click="onRemoveMember(organization.team_id, member)"
>
<UserRemoveIcon />
Remove member
</Button>
<Button
v-if="!member.is_owner && currentMember.is_owner && member.accepted"
@click="() => onTransferOwnership(organization.team_id, member.user.id)"
>
<TransferIcon />
Transfer ownership
</Button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
CrownIcon,
DropdownIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UserXIcon as UserRemoveIcon,
} from "@modrinth/assets";
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from "@modrinth/ui";
import { ref } from "vue";
import { removeTeamMember } from "~/helpers/teams.js";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
import { isPermission } from "~/utils/permissions.ts";
CrownIcon,
DropdownIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UserXIcon as UserRemoveIcon,
} from '@modrinth/assets'
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue'
const { addNotification } = injectNotificationManager();
const { organization, refresh: refreshOrganization, currentMember } = injectOrganizationContext();
import { removeTeamMember } from '~/helpers/teams.js'
import { injectOrganizationContext } from '~/providers/organization-context.ts'
import { isPermission } from '~/utils/permissions.ts'
const auth = await useAuth();
const { addNotification } = injectNotificationManager()
const { organization, refresh: refreshOrganization, currentMember } = injectOrganizationContext()
const currentUsername = ref("");
const openTeamMembers = ref([]);
const auth = await useAuth()
const allTeamMembers = ref(organization.value.members);
const currentUsername = ref('')
const openTeamMembers = ref([])
const allTeamMembers = ref(organization.value.members)
watch(
() => organization.value,
() => {
allTeamMembers.value = organization.value.members;
},
);
() => organization.value,
() => {
allTeamMembers.value = organization.value.members
},
)
const projectPermissions = {
UPLOAD_VERSION: 1 << 0,
DELETE_VERSION: 1 << 1,
EDIT_DETAILS: 1 << 2,
EDIT_BODY: 1 << 3,
MANAGE_INVITES: 1 << 4,
REMOVE_MEMBER: 1 << 5,
EDIT_MEMBER: 1 << 6,
DELETE_PROJECT: 1 << 7,
VIEW_ANALYTICS: 1 << 8,
VIEW_PAYOUTS: 1 << 9,
};
UPLOAD_VERSION: 1 << 0,
DELETE_VERSION: 1 << 1,
EDIT_DETAILS: 1 << 2,
EDIT_BODY: 1 << 3,
MANAGE_INVITES: 1 << 4,
REMOVE_MEMBER: 1 << 5,
EDIT_MEMBER: 1 << 6,
DELETE_PROJECT: 1 << 7,
VIEW_ANALYTICS: 1 << 8,
VIEW_PAYOUTS: 1 << 9,
}
const organizationPermissions = {
EDIT_DETAILS: 1 << 0,
MANAGE_INVITES: 1 << 1,
REMOVE_MEMBER: 1 << 2,
EDIT_MEMBER: 1 << 3,
ADD_PROJECT: 1 << 4,
REMOVE_PROJECT: 1 << 5,
DELETE_ORGANIZATION: 1 << 6,
EDIT_MEMBER_DEFAULT_PERMISSIONS: 1 << 7,
};
EDIT_DETAILS: 1 << 0,
MANAGE_INVITES: 1 << 1,
REMOVE_MEMBER: 1 << 2,
EDIT_MEMBER: 1 << 3,
ADD_PROJECT: 1 << 4,
REMOVE_PROJECT: 1 << 5,
DELETE_ORGANIZATION: 1 << 6,
EDIT_MEMBER_DEFAULT_PERMISSIONS: 1 << 7,
}
const permToLabel = (key) => {
const o = key.split("_").join(" ");
return o.charAt(0).toUpperCase() + o.slice(1).toLowerCase();
};
const o = key.split('_').join(' ')
return o.charAt(0).toUpperCase() + o.slice(1).toLowerCase()
}
const leaveProject = async (teamId, uid) => {
await removeTeamMember(teamId, uid);
await navigateTo(`/organization/${organization.value.id}`);
};
await removeTeamMember(teamId, uid)
await navigateTo(`/organization/${organization.value.id}`)
}
const onLeaveProject = useClientTry(leaveProject);
const onLeaveProject = useClientTry(leaveProject)
const onInviteTeamMember = useClientTry(async (teamId, username) => {
const user = await useBaseFetch(`user/${username}`);
const data = {
user_id: user.id.trim(),
};
await useBaseFetch(`team/${teamId}/members`, {
method: "POST",
body: data,
});
await refreshOrganization();
currentUsername.value = "";
addNotification({
title: "Member invited",
text: `${user.username} has been invited to the organization.`,
type: "success",
});
});
const user = await useBaseFetch(`user/${username}`)
const data = {
user_id: user.id.trim(),
}
await useBaseFetch(`team/${teamId}/members`, {
method: 'POST',
body: data,
})
await refreshOrganization()
currentUsername.value = ''
addNotification({
title: 'Member invited',
text: `${user.username} has been invited to the organization.`,
type: 'success',
})
})
const onRemoveMember = useClientTry(async (teamId, member) => {
await removeTeamMember(teamId, member.user.id);
await refreshOrganization();
addNotification({
title: "Member removed",
text: `${member.user.username} has been removed from the organization.`,
type: "success",
});
});
await removeTeamMember(teamId, member.user.id)
await refreshOrganization()
addNotification({
title: 'Member removed',
text: `${member.user.username} has been removed from the organization.`,
type: 'success',
})
})
const onUpdateTeamMember = useClientTry(async (teamId, member) => {
const data = !member.is_owner
? {
permissions: member.permissions,
organization_permissions: member.organization_permissions,
role: member.role,
payouts_split: member.payouts_split,
}
: {
payouts_split: member.payouts_split,
role: member.role,
};
await useBaseFetch(`team/${teamId}/members/${member.user.id}`, {
method: "PATCH",
body: data,
});
await refreshOrganization();
addNotification({
title: "Member updated",
text: `${member.user.username} has been updated.`,
type: "success",
});
});
const data = !member.is_owner
? {
permissions: member.permissions,
organization_permissions: member.organization_permissions,
role: member.role,
payouts_split: member.payouts_split,
}
: {
payouts_split: member.payouts_split,
role: member.role,
}
await useBaseFetch(`team/${teamId}/members/${member.user.id}`, {
method: 'PATCH',
body: data,
})
await refreshOrganization()
addNotification({
title: 'Member updated',
text: `${member.user.username} has been updated.`,
type: 'success',
})
})
const onTransferOwnership = useClientTry(async (teamId, uid) => {
const data = {
user_id: uid,
};
await useBaseFetch(`team/${teamId}/owner`, {
method: "PATCH",
body: data,
});
await refreshOrganization();
addNotification({
title: "Ownership transferred",
text: `The ownership of ${organization.value.name} has been successfully transferred.`,
type: "success",
});
});
const data = {
user_id: uid,
}
await useBaseFetch(`team/${teamId}/owner`, {
method: 'PATCH',
body: data,
})
await refreshOrganization()
addNotification({
title: 'Ownership transferred',
text: `The ownership of ${organization.value.name} has been successfully transferred.`,
type: 'success',
})
})
</script>
<style lang="scss" scoped>
.member {
.member-header {
display: flex;
justify-content: space-between;
.member-header {
display: flex;
justify-content: space-between;
.info {
display: flex;
.info {
display: flex;
.text {
margin: auto 0 auto 0.5rem;
font-size: var(--font-size-sm);
.text {
margin: auto 0 auto 0.5rem;
font-size: var(--font-size-sm);
.name {
font-weight: bold;
.name {
font-weight: bold;
display: flex;
align-items: center;
gap: 0.25rem;
display: flex;
align-items: center;
gap: 0.25rem;
svg {
color: var(--color-orange);
}
}
svg {
color: var(--color-orange);
}
}
p {
margin: 0.2rem 0;
}
}
}
p {
margin: 0.2rem 0;
}
}
}
.side-buttons {
display: flex;
align-items: center;
.side-buttons {
display: flex;
align-items: center;
.dropdown-icon {
margin-left: 1rem;
.dropdown-icon {
margin-left: 1rem;
svg {
transition: 150ms ease transform;
}
}
}
}
svg {
transition: 150ms ease transform;
}
}
}
}
.content {
display: none;
flex-direction: column;
padding-top: var(--gap-md);
.content {
display: none;
flex-direction: column;
padding-top: var(--gap-md);
.main-info {
margin-bottom: var(--gap-lg);
}
.main-info {
margin-bottom: var(--gap-lg);
}
.permissions {
margin-bottom: var(--gap-md);
max-width: 45rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: 0.5rem;
}
}
.permissions {
margin-bottom: var(--gap-md);
max-width: 45rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: 0.5rem;
}
}
&.open {
.member-header {
.dropdown-icon svg {
transform: rotate(180deg);
}
}
&.open {
.member-header {
.dropdown-icon svg {
transform: rotate(180deg);
}
}
.content {
display: flex;
}
}
.content {
display: flex;
}
}
}
:deep(.checkbox-outer) {
button.checkbox {
border: none;
}
button.checkbox {
border: none;
}
}
</style>

File diff suppressed because it is too large Load Diff