enable member deletion and reject pending + bugs
This commit is contained in:
@@ -42,7 +42,7 @@ Please don't hitch your wagon to this star just yet... I'm coding this in the op
|
|||||||
- [x] Gen/Regen an invite link to allow users to join a team
|
- [x] Gen/Regen an invite link to allow users to join a team
|
||||||
- [x] Team administrators and owners can accept pending invites
|
- [x] Team administrators and owners can accept pending invites
|
||||||
- [x] Team administrators and owners can administer the permissions (roles) of other team members on the Accounts page
|
- [x] Team administrators and owners can administer the permissions (roles) of other team members on the Accounts page
|
||||||
- [ ] Team owners can remove users from team
|
- [x] Team owners can remove users from team
|
||||||
|
|
||||||
### Plans and Pricing
|
### Plans and Pricing
|
||||||
- [x] Manage multiple Plans each with specific Feature flags and Plan limits
|
- [x] Manage multiple Plans each with specific Feature flags and Plan limits
|
||||||
@@ -63,7 +63,6 @@ Please don't hitch your wagon to this star just yet... I'm coding this in the op
|
|||||||
### Look and Feel, Design System and Customisation
|
### Look and Feel, Design System and Customisation
|
||||||
- [x] Default UI isn't too crap
|
- [x] Default UI isn't too crap
|
||||||
- [x] Integrated Design system including theming (Tailwind + daisyUI)
|
- [x] Integrated Design system including theming (Tailwind + daisyUI)
|
||||||
- [ ] Branding options (logo, color scheme, etc.)
|
|
||||||
|
|
||||||
### Demo Software (Notes)
|
### Demo Software (Notes)
|
||||||
- [x] Simple Text based Notes functionality
|
- [x] Simple Text based Notes functionality
|
||||||
@@ -73,10 +72,6 @@ Please don't hitch your wagon to this star just yet... I'm coding this in the op
|
|||||||
- [x] Max Notes enforced
|
- [x] Max Notes enforced
|
||||||
- [x] Add, Delete notes on Dashboard
|
- [x] Add, Delete notes on Dashboard
|
||||||
|
|
||||||
### Mobile App
|
|
||||||
- [ ] Flutter App Demo integrating with API endpoints, Auth etc
|
|
||||||
- [ ] Mobile-friendly web interface.
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
- [ ] Unit tests for server functions
|
- [ ] Unit tests for server functions
|
||||||
- [ ] Integration tests around subscription scenarios
|
- [ ] Integration tests around subscription scenarios
|
||||||
|
|||||||
@@ -52,11 +52,12 @@
|
|||||||
<ul tabindex="0" class="mt-3 p-2 shadow menu menu-compact dropdown-content bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="mt-3 p-2 shadow menu menu-compact dropdown-content bg-base-100 rounded-box w-52">
|
||||||
<li v-if="user">{{ user.email }}</li>
|
<li v-if="user">{{ user.email }}</li>
|
||||||
<li><NuxtLink to="/account">Account</NuxtLink></li>
|
<li><NuxtLink to="/account">Account</NuxtLink></li>
|
||||||
<li><a href="#" @click.prevent="signout()">logout</a></li>
|
<li><a href="#" @click.prevent="signout()">Signout</a></li>
|
||||||
<template v-if="dbUser?.memberships && dbUser?.memberships.length > 1">
|
<template v-if="dbUser?.memberships && dbUser?.memberships.length > 1">
|
||||||
<li>Switch Account</li>
|
<li>Switch Account</li>
|
||||||
<li v-for="membership in dbUser?.memberships" :disabled="membership.pending">
|
<li v-for="membership in dbUser?.memberships">
|
||||||
<a v-if="membership.account_id !== activeAccountId" href="#" @click="accountStore.changeActiveAccount(membership.account_id)">{{ membership.account.name }}<span v-if="membership.pending">(pending)</span></a>
|
<a v-if="membership.account_id !== activeAccountId && !membership.pending" href="#" @click="accountStore.changeActiveAccount(membership.account_id)">{{ membership.account.name }}</a>
|
||||||
|
<span v-if="membership.pending">{{ membership.account.name }} (pending)</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -92,6 +92,25 @@ export default class AccountService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteMembership(account_id: number, membership_id: number): Promise<MembershipWithAccount> {
|
||||||
|
const membership = prisma_client.membership.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: membership_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if((await membership).account_id != account_id){
|
||||||
|
throw new Error(`Membership does not belong to current account`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma_client.membership.delete({
|
||||||
|
where: {
|
||||||
|
id: membership_id,
|
||||||
|
},
|
||||||
|
...membershipWithAccount
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async joinUserToAccount(user_id: number, account_id: number, pending: boolean ): Promise<MembershipWithAccount> {
|
async joinUserToAccount(user_id: number, account_id: number, pending: boolean ): Promise<MembershipWithAccount> {
|
||||||
const account = await prisma_client.account.findUnique({
|
const account = await prisma_client.account.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -13,29 +13,32 @@
|
|||||||
await accountStore.getActiveAccountMembers();
|
await accountStore.getActiveAccountMembers();
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatDate(date: Date | undefined){
|
function formatDate(date: Date | undefined) {
|
||||||
if(!date){ return ""; }
|
if (!date) { return ""; }
|
||||||
return new Intl.DateTimeFormat('default', {dateStyle: 'long'}).format(date);
|
return new Intl.DateTimeFormat('default', { dateStyle: 'long' }).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinURL(){
|
function joinURL() {
|
||||||
return `${config.public.siteRootUrl}/join/${activeMembership.value?.account.join_password}`;
|
return `${config.public.siteRootUrl}/join/${activeMembership.value?.account.join_password}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto p-6">
|
<div class="container mx-auto p-6">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Account Information</h2>
|
<h2 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl md:text-6xl mb-4">Account Information
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex gap-4 items-center">
|
<div class="flex gap-4 items-center">
|
||||||
<span class="font-bold w-32">Account Name:</span>
|
<span class="font-bold w-32">Account Name:</span>
|
||||||
<span>{{ activeMembership?.account.name }}</span>
|
<span>{{ activeMembership?.account.name }}</span>
|
||||||
<template v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)">
|
<template
|
||||||
<input v-model="newAccountName" type="text" class="p-2 border border-gray-400 rounded w-1/3" placeholder="Enter new name">
|
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)">
|
||||||
<button @click.prevent="accountStore.changeAccountName(newAccountName)" class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">Change Name</button>
|
<input v-model="newAccountName" type="text" class="p-2 border border-gray-400 rounded w-1/3"
|
||||||
|
placeholder="Enter new name">
|
||||||
|
<button @click.prevent="accountStore.changeAccountName(newAccountName)"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded">Change Name</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,7 +50,8 @@
|
|||||||
<div class="flex gap-4 items-center">
|
<div class="flex gap-4 items-center">
|
||||||
<span class="font-bold w-32">Permitted Features:</span>
|
<span class="font-bold w-32">Permitted Features:</span>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span v-for="feature in activeMembership?.account.features" class="bg-gray-200 text-gray-700 font-semibold py-1 px-2 rounded-full">{{ feature }}</span>
|
<span v-for="feature in activeMembership?.account.features"
|
||||||
|
class="bg-gray-200 text-gray-700 font-semibold py-1 px-2 rounded-full">{{ feature }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,7 +74,9 @@
|
|||||||
<span class="font-bold w-32">Join Link:</span>
|
<span class="font-bold w-32">Join Link:</span>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<input disabled type="text" class="p-2 border border-gray-400 rounded w-full" :value="joinURL()">
|
<input disabled type="text" class="p-2 border border-gray-400 rounded w-full" :value="joinURL()">
|
||||||
<button @click.prevent="accountStore.rotateJoinPassword()" v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">ReGen</button>
|
<button @click.prevent="accountStore.rotateJoinPassword()"
|
||||||
|
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||||
|
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">ReGen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -81,11 +87,33 @@
|
|||||||
<span>{{ accountMember.user.display_name }}</span>
|
<span>{{ accountMember.user.display_name }}</span>
|
||||||
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ accountMember.access }}</span>
|
<span class="bg-green-500 text-white font-semibold py-1 px-2 rounded-full">{{ accountMember.access }}</span>
|
||||||
<span class="text-gray-500">({{ accountMember.user.email }})</span>
|
<span class="text-gray-500">({{ accountMember.user.email }})</span>
|
||||||
<button @click.prevent="" v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access !== ACCOUNT_ACCESS.OWNER" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">Remove</button>
|
<button @click.prevent="accountStore.acceptPendingMembership(accountMember.id)"
|
||||||
|
v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||||
|
Accept Pending Membership
|
||||||
|
</button>
|
||||||
|
<button @click.prevent="accountStore.rejectPendingMembership(accountMember.id)"
|
||||||
|
v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"
|
||||||
|
class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||||
|
Reject Pending Membership
|
||||||
|
</button>
|
||||||
|
<button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.READ_WRITE)"
|
||||||
|
v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_ONLY && !accountMember.pending"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||||
|
Promote to Read/Write
|
||||||
|
</button>
|
||||||
|
<button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.ADMIN)"
|
||||||
|
v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access === ACCOUNT_ACCESS.READ_WRITE && !accountMember.pending"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||||
|
Promote to Admin
|
||||||
|
</button>
|
||||||
|
<button @click.prevent="accountStore.deleteMembership(accountMember.id)"
|
||||||
|
v-if="activeMembership && activeMembership.access === ACCOUNT_ACCESS.OWNER && accountMember.access !== ACCOUNT_ACCESS.OWNER && !accountMember.pending"
|
||||||
|
class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-blue">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div></template>
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
async function doJoin() {
|
async function doJoin() {
|
||||||
if (account) {
|
if (account) {
|
||||||
await accountStore.joinUserToAccountPending(account.id);
|
await accountStore.joinUserToAccountPending(account.id);
|
||||||
|
navigateTo('/dashboard', {replace: true})
|
||||||
} else {
|
} else {
|
||||||
console.log(`Unable to Join`)
|
console.log(`Unable to Join`)
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-10 px-4 sm:px-6 lg:px-8">
|
<div class="py-10 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-md mx-auto">
|
<div class="max-w-md mx-auto">
|
||||||
<h2 class="text-3xl font-extrabold text-gray-900">Request to Join Bob's Team</h2>
|
<h2 class="text-3xl font-extrabold text-gray-900">Request to Join {{ account?.name }}</h2>
|
||||||
<template v-if="dbUser?.dbUser">
|
<template v-if="dbUser?.dbUser">
|
||||||
<p class="mt-2 text-sm text-gray-500">
|
<p class="mt-2 text-sm text-gray-500">
|
||||||
Click below to request to Join the team.
|
Click below to request to Join the team.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { router, adminProcedure, publicProcedure, protectedProcedure } from '../trpc'
|
import { TRPCError } from '@trpc/server';
|
||||||
|
import { router, adminProcedure, publicProcedure, protectedProcedure, ownerProcedure } from '../trpc'
|
||||||
import { ACCOUNT_ACCESS } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import AccountService from '~~/lib/services/account.service';
|
import AccountService from '~~/lib/services/account.service';
|
||||||
@@ -23,6 +24,10 @@ export const accountRouter = router({
|
|||||||
changeActiveAccount: protectedProcedure
|
changeActiveAccount: protectedProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.input(z.object({ account_id: z.number() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const activeMembership = ctx.dbUser?.memberships.find(membership => membership.account_id == input.account_id);
|
||||||
|
if(activeMembership?.pending){
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message:`membership ${activeMembership?.id} is not active so cannot be switched to` });
|
||||||
|
}
|
||||||
ctx.activeAccountId = input.account_id;
|
ctx.activeAccountId = input.account_id;
|
||||||
setCookie(ctx.event, 'preferred-active-account-id', input.account_id.toString(), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10)});
|
setCookie(ctx.event, 'preferred-active-account-id', input.account_id.toString(), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10)});
|
||||||
}),
|
}),
|
||||||
@@ -70,6 +75,24 @@ export const accountRouter = router({
|
|||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
rejectPendingMembership: adminProcedure
|
||||||
|
.input(z.object({ membership_id: z.number() }))
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const accountService = new AccountService();
|
||||||
|
const membership: MembershipWithAccount = await accountService.deleteMembership(ctx.activeAccountId!, input.membership_id);
|
||||||
|
return {
|
||||||
|
membership,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteMembership: ownerProcedure
|
||||||
|
.input(z.object({ membership_id: z.number() }))
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const accountService = new AccountService();
|
||||||
|
const membership: MembershipWithAccount = await accountService.deleteMembership(ctx.activeAccountId!, input.membership_id);
|
||||||
|
return {
|
||||||
|
membership,
|
||||||
|
}
|
||||||
|
}),
|
||||||
changeUserAccessWithinAccount: adminProcedure
|
changeUserAccessWithinAccount: adminProcedure
|
||||||
.input(z.object({ user_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
.input(z.object({ user_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import NotesService from '~~/lib/services/notes.service';
|
import NotesService from '~~/lib/services/notes.service';
|
||||||
import { protectedProcedure, publicProcedure, router } from '../trpc';
|
import { memberProcedure, protectedProcedure, publicProcedure, router } from '../trpc';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const notesRouter = router({
|
export const notesRouter = router({
|
||||||
getForCurrentUser: protectedProcedure
|
getForCurrentUser: memberProcedure
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService();
|
const notesService = new NotesService();
|
||||||
const notes = (ctx.activeAccountId)?await notesService.getNotesForAccountId(ctx.activeAccountId):[];
|
const notes = (ctx.activeAccountId)?await notesService.getNotesForAccountId(ctx.activeAccountId):[];
|
||||||
@@ -20,7 +20,7 @@ export const notesRouter = router({
|
|||||||
note,
|
note,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createNote: protectedProcedure
|
createNote: memberProcedure
|
||||||
.input(z.object({ note_text: z.string() }))
|
.input(z.object({ note_text: z.string() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService();
|
const notesService = new NotesService();
|
||||||
@@ -29,7 +29,7 @@ export const notesRouter = router({
|
|||||||
note,
|
note,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
deleteNote: protectedProcedure
|
deleteNote: memberProcedure
|
||||||
.input(z.object({ note_id: z.number() }))
|
.input(z.object({ note_id: z.number() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService();
|
const notesService = new NotesService();
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ const isAuthed = t.middleware(({ next, ctx }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isMemberForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
||||||
|
if (!ctx.dbUser || !ctx.activeAccountId) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
|
}
|
||||||
|
const activeMembership = ctx.dbUser.memberships.find(membership => membership.account_id == ctx.activeAccountId);
|
||||||
|
if(!activeMembership || activeMembership.pending) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED', message:`membership ${activeMembership?.id} is not active` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ ctx });
|
||||||
|
});
|
||||||
|
|
||||||
const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
||||||
if (!ctx.dbUser || !ctx.activeAccountId) {
|
if (!ctx.dbUser || !ctx.activeAccountId) {
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
@@ -42,11 +54,25 @@ const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
|||||||
return next({ ctx });
|
return next({ ctx });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isOwnerForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
||||||
|
if (!ctx.dbUser || !ctx.activeAccountId) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
|
}
|
||||||
|
const activeMembership = ctx.dbUser.memberships.find(membership => membership.account_id == ctx.activeAccountId);
|
||||||
|
if(!activeMembership || activeMembership?.access !== ACCOUNT_ACCESS.OWNER) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED', message:`activeMembership ${activeMembership?.id} is only ${activeMembership?.access}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ ctx });
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Procedures
|
* Procedures
|
||||||
**/
|
**/
|
||||||
export const publicProcedure = t.procedure;
|
export const publicProcedure = t.procedure;
|
||||||
export const protectedProcedure = t.procedure.use(isAuthed);
|
export const protectedProcedure = t.procedure.use(isAuthed);
|
||||||
|
export const memberProcedure = protectedProcedure.use(isMemberForInputAccountId);
|
||||||
export const adminProcedure = protectedProcedure.use(isAdminForInputAccountId);
|
export const adminProcedure = protectedProcedure.use(isAdminForInputAccountId);
|
||||||
|
export const ownerProcedure = protectedProcedure.use(isOwnerForInputAccountId);
|
||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
export const middleware = t.middleware;
|
export const middleware = t.middleware;
|
||||||
|
|||||||
@@ -96,6 +96,22 @@ export const useAccountStore = defineStore('account', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async rejectPendingMembership(membership_id: number){
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
|
const { data: membership } = await $client.account.rejectPendingMembership.useQuery({ membership_id });
|
||||||
|
|
||||||
|
if(membership.value){
|
||||||
|
this.activeAccountMembers = this.activeAccountMembers.filter(m => m.id !== membership_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteMembership(membership_id: number){
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
|
const { data: membership } = await $client.account.deleteMembership.useQuery({ membership_id });
|
||||||
|
|
||||||
|
if(membership.value){
|
||||||
|
this.activeAccountMembers = this.activeAccountMembers.filter(m => m.id !== membership_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
async rotateJoinPassword(){
|
async rotateJoinPassword(){
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { account } = await $client.account.rotateJoinPassword.mutate();
|
const { account } = await $client.account.rotateJoinPassword.mutate();
|
||||||
|
|||||||
Reference in New Issue
Block a user