GameCraftGameCraft

Organization & Team Management

Complete guide to workspace management, member invitations, role permissions, and team collaboration

Organization & Team Management

ProductReady uses Better Auth's organization plugin to implement complete multi-tenant collaboration features. This guide covers workspace management, member invitations, role permissions, and team collaboration.

Terminology: In ProductReady, "Workspace" (Space) and "Organization" refer to the same concept. The code uses Organization, while the user interface displays Space.


Quick Overview

Core Features:

  • ✅ Create and manage multiple workspaces
  • ✅ Invite members to workspaces
  • ✅ Role-based access control (RBAC)
  • ✅ Team/Group management
  • ✅ Workspace settings and billing management
  • ✅ Leave or delete workspaces

Role Hierarchy:

  • Owner: Full permissions, including workspace deletion
  • Admin: Nearly full permissions, cannot delete workspace or modify billing
  • Member: Basic work permissions, can create and edit content
  • Viewer: Read-only permissions, can view but not modify

Workspace (Space) Management

Create Workspace

After logging in, users can create multiple workspaces for different projects or teams.

How to create:

  1. Click the workspace selector at the top of the left sidebar
  2. Click "+ New Workspace" button
  3. Enter workspace name and description
  4. Click "Create"
'use client';

import { authClient } from '~/lib/auth/client';
import { useState } from 'react';

export function CreateSpaceButton() {
  const [name, setName] = useState('');
  const [slug, setSlug] = useState('');

  async function handleCreate() {
    const result = await authClient.organization.create({
      name,
      slug, // Optional, for URL
    });

    if (result.error) {
      console.error('Create failed:', result.error);
      return;
    }

    console.log('Workspace created:', result.data);
  }

  return (
    <div>
      <input
        type="text"
        placeholder="Workspace name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button onClick={handleCreate}>Create Workspace</button>
    </div>
  );
}

Switch Workspace

Users can quickly switch between multiple workspaces.

How to switch:

  1. Click the workspace selector at the top of the left sidebar
  2. Select the target workspace from the list
  3. The page automatically switches to the new workspace context
'use client';

import { authClient } from '~/lib/auth/client';

export function SwitchSpace({ organizationId }: { organizationId: string }) {
  async function handleSwitch() {
    await authClient.organization.setActive({
      organizationId,
    });
    
    // Refresh page to update context
    window.location.reload();
  }

  return (
    <button onClick={handleSwitch}>
      Switch to this workspace
    </button>
  );
}

Update Workspace Info

Workspace owners and admins can update basic workspace information.

'use client';

import { authClient } from '~/lib/auth/client';

export function UpdateSpace() {
  async function handleUpdate(organizationId: string, updates: { name?: string; slug?: string }) {
    const result = await authClient.organization.update({
      organizationId,
      data: updates,
    });

    if (result.error) {
      console.error('Update failed:', result.error);
      return;
    }

    console.log('Workspace updated');
  }

  return (
    <button onClick={() => handleUpdate('org-id', { name: 'New Name' })}>
      Update Workspace
    </button>
  );
}

Delete Workspace

Only workspace owners can delete workspaces. Deletion is irreversible.

Steps to delete:

  1. Open workspace settings (click "Settings" button in sidebar footer)
  2. Switch to "General" tab
  3. Scroll to "Danger Zone"
  4. Click "Delete Workspace" button
  5. Enter workspace name to confirm
  6. Click "Confirm Deletion"

Warning: Deleting a workspace will permanently delete all related data, including members, teams, projects, documents, etc. This action cannot be undone!

'use client';

import { authClient } from '~/lib/auth/client';

export function DeleteSpace({ organizationId }: { organizationId: string }) {
  async function handleDelete() {
    const confirmed = window.confirm('Are you sure you want to delete this workspace? All data will be permanently deleted!');
    if (!confirmed) return;

    const result = await authClient.organization.delete({
      organizationId,
    });

    if (result.error) {
      console.error('Delete failed:', result.error);
      return;
    }

    // Deletion successful, redirect to workspace list
    window.location.href = '/spaces';
  }

  return (
    <button onClick={handleDelete} className="btn-danger">
      Delete Workspace
    </button>
  );
}

Member Management

Invite Members

Admins and owners can invite new members to the workspace via email.

Steps to invite:

  1. Open workspace settings → "People" tab
  2. Click "Invite Member" button
  3. Enter member's email address
  4. Select role (Member, Admin, Viewer)
  5. Click "Send Invitation"

Invitees will receive an invitation email and can accept by clicking the link.

'use client';

import { authClient } from '~/lib/auth/client';
import { useState } from 'react';

export function InviteMemberForm() {
  const [email, setEmail] = useState('');
  const [role, setRole] = useState<'member' | 'admin'>('member');

  async function handleInvite() {
    const result = await authClient.organization.inviteMember({
      email,
      role,
    });

    if (result.error) {
      console.error('Invite failed:', result.error);
      return;
    }

    alert('Invitation sent!');
    setEmail('');
  }

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleInvite(); }}>
      <input
        type="email"
        placeholder="Member email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      <select value={role} onChange={(e) => setRole(e.target.value as any)}>
        <option value="member">Member</option>
        <option value="admin">Admin</option>
      </select>
      <button type="submit">Send Invitation</button>
    </form>
  );
}

View Pending Invitations

Admins can view and manage all pending invitations.

How to view:

  1. Open workspace settings → "People" tab
  2. See "Pending Invitations" section below member list
  3. Can cancel any pending invitation
'use client';

import { authClient } from '~/lib/auth/client';
import { useEffect, useState } from 'react';

interface Invitation {
  id: string;
  email: string;
  role: string;
  status: string;
  expiresAt: Date;
}

export function PendingInvitations() {
  const [invitations, setInvitations] = useState<Invitation[]>([]);

  useEffect(() => {
    loadInvitations();
  }, []);

  async function loadInvitations() {
    const result = await authClient.organization.listInvitations();
    if (result.data) {
      setInvitations(result.data as Invitation[]);
    }
  }

  async function cancelInvitation(invitationId: string) {
    await authClient.organization.cancelInvitation({
      invitationId,
    });
    loadInvitations(); // Refresh list
  }

  return (
    <div>
      <h3>Pending Invitations</h3>
      {invitations.map((inv) => (
        <div key={inv.id}>
          <span>{inv.email} - {inv.role}</span>
          <button onClick={() => cancelInvitation(inv.id)}>
            Cancel Invitation
          </button>
        </div>
      ))}
    </div>
  );
}

Accept or Reject Invitations

Invited users can view and manage all received invitations.

View invitations:

  1. After logging in, if there are pending invitations, you'll see "My Invitations" button on the people settings page
  2. Click the button to view all invitations
  3. Can choose to accept or reject
'use client';

import { authClient } from '~/lib/auth/client';
import { useEffect, useState } from 'react';

export function UserInvitations() {
  const [invitations, setInvitations] = useState<any[]>([]);

  useEffect(() => {
    loadUserInvitations();
  }, []);

  async function loadUserInvitations() {
    const result = await authClient.organization.listUserInvitations();
    if (result.data) {
      setInvitations(result.data);
    }
  }

  async function acceptInvitation(invitationId: string) {
    const result = await authClient.organization.acceptInvitation({
      invitationId,
    });

    if (!result.error) {
      alert('Joined workspace!');
      loadUserInvitations();
    }
  }

  async function rejectInvitation(invitationId: string) {
    const result = await authClient.organization.rejectInvitation({
      invitationId,
    });

    if (!result.error) {
      loadUserInvitations();
    }
  }

  return (
    <div>
      <h3>My Invitations</h3>
      {invitations.map((inv) => (
        <div key={inv.id}>
          <p>Invited to join: {inv.organizationName}</p>
          <p>Role: {inv.role}</p>
          <button onClick={() => acceptInvitation(inv.id)}>Accept</button>
          <button onClick={() => rejectInvitation(inv.id)}>Reject</button>
        </div>
      ))}
    </div>
  );
}

Change Member Role

Admins and owners can change member roles.

Steps to change:

  1. Open workspace settings → "People" tab
  2. Find the member to modify
  3. Click role badge or action button
  4. Select new role
  5. Confirm change
'use client';

import { authClient } from '~/lib/auth/client';

export function UpdateMemberRole({
  memberId,
  newRole,
}: {
  memberId: string;
  newRole: 'member' | 'admin';
}) {
  async function handleUpdate() {
    const result = await authClient.organization.updateMemberRole({
      memberIdOrEmail: memberId,
      role: newRole,
    });

    if (result.error) {
      console.error('Update failed:', result.error);
      return;
    }

    alert('Role updated!');
  }

  return <button onClick={handleUpdate}>Change to {newRole}</button>;
}

Remove Member

Admins and owners can remove members from the workspace.

'use client';

import { authClient } from '~/lib/auth/client';

export function RemoveMember({ memberId }: { memberId: string }) {
  async function handleRemove() {
    const confirmed = window.confirm('Are you sure you want to remove this member?');
    if (!confirmed) return;

    const result = await authClient.organization.removeMember({
      memberIdOrEmail: memberId,
    });

    if (result.error) {
      console.error('Remove failed:', result.error);
      return;
    }

    alert('Member removed');
  }

  return <button onClick={handleRemove}>Remove Member</button>;
}

Leave Workspace

Non-owner members can voluntarily leave a workspace.

Steps to leave:

  1. Open workspace settings → "People" tab
  2. Find yourself in the member list
  3. Click "Leave" button
  4. Confirm action

Note: Owners cannot leave their own workspace; they must first transfer ownership or delete the workspace.

'use client';

import { authClient } from '~/lib/auth/client';
import { useRouter } from 'next/navigation';

export function LeaveOrganization({ organizationId }: { organizationId: string }) {
  const router = useRouter();

  async function handleLeave() {
    const confirmed = window.confirm('Are you sure you want to leave this workspace? You will lose access to all content.');
    if (!confirmed) return;

    const result = await authClient.organization.leave({
      organizationId,
    });

    if (result.error) {
      console.error('Leave failed:', result.error);
      return;
    }

    // Redirect to workspace list
    router.push('/spaces');
  }

  return <button onClick={handleLeave}>Leave Workspace</button>;
}

Team/Group Management

Teams (Team/Group) are sub-units within a workspace, used to organize members and assign permissions.

Create Team

Admins and owners can create teams.

Steps to create:

  1. Open workspace settings → "People" tab → "Groups" sub-tab
  2. Click "Create Team" button
  3. Enter team name
  4. Click "Create"
'use client';

import { authClient } from '~/lib/auth/client';
import { useState } from 'react';

export function CreateTeamForm() {
  const [name, setName] = useState('');

  async function handleCreate() {
    const result = await authClient.organization.createTeam({
      name,
    });

    if (result.error) {
      console.error('Create failed:', result.error);
      return;
    }

    alert('Team created!');
    setName('');
  }

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleCreate(); }}>
      <input
        type="text"
        placeholder="Team name"
        value={name}
        onChange={(e) => setName(e.target.value)}
        required
      />
      <button type="submit">Create Team</button>
    </form>
  );
}

Add Member to Team

Admins can add workspace members to specific teams.

'use client';

import { authClient } from '~/lib/auth/client';

export function AddMemberToTeam({
  teamId,
  memberIdOrEmail,
}: {
  teamId: string;
  memberIdOrEmail: string;
}) {
  async function handleAdd() {
    const result = await authClient.organization.addTeamMember({
      teamId,
      memberIdOrEmail,
    });

    if (result.error) {
      console.error('Add failed:', result.error);
      return;
    }

    alert('Member added to team!');
  }

  return <button onClick={handleAdd}>Add to Team</button>;
}

Remove Member from Team

'use client';

import { authClient } from '~/lib/auth/client';

export function RemoveMemberFromTeam({
  teamId,
  memberIdOrEmail,
}: {
  teamId: string;
  memberIdOrEmail: string;
}) {
  async function handleRemove() {
    const result = await authClient.organization.removeTeamMember({
      teamId,
      memberIdOrEmail,
    });

    if (result.error) {
      console.error('Remove failed:', result.error);
      return;
    }

    alert('Member removed from team');
  }

  return <button onClick={handleRemove}>Remove from Team</button>;
}

View Team Members

'use client';

import { authClient } from '~/lib/auth/client';
import { useEffect, useState } from 'react';

export function TeamMembers({ teamId }: { teamId: string }) {
  const [members, setMembers] = useState<any[]>([]);

  useEffect(() => {
    loadMembers();
  }, []);

  async function loadMembers() {
    const result = await authClient.organization.listTeamMembers({
      teamId,
    });

    if (result.data) {
      setMembers(result.data);
    }
  }

  return (
    <div>
      <h3>Team Members</h3>
      <ul>
        {members.map((member) => (
          <li key={member.id}>
            {member.user.name} - {member.role}
          </li>
        ))}
      </ul>
    </div>
  );
}

Delete Team

'use client';

import { authClient } from '~/lib/auth/client';

export function DeleteTeam({ teamId }: { teamId: string }) {
  async function handleDelete() {
    const confirmed = window.confirm('Are you sure you want to delete this team?');
    if (!confirmed) return;

    const result = await authClient.organization.deleteTeam({
      teamId,
    });

    if (result.error) {
      console.error('Delete failed:', result.error);
      return;
    }

    alert('Team deleted');
  }

  return <button onClick={handleDelete}>Delete Team</button>;
}

Permission System

ProductReady implements role-based access control (RBAC), supporting fine-grained permission management.

Role & Permission Matrix

ResourceOwnerAdminMemberViewer
Workspace
View settings
Update settings
Delete workspace
Member Management
Invite members
Remove members
Change roles
Team Management
Create teams
Delete teams
Manage team members
Projects
Create projects
View projects
Edit projects
Delete projects
Billing
View billing
Update billing

Check Permissions in Code

Use the usePermissions hook to check permissions in React components:

'use client';

import { usePermissions } from '~/lib/auth/client';

export function ProjectActions() {
  const { can, canManage, role, isLoading } = usePermissions();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <p>Current role: {role}</p>
      
      {can('project', 'create') && (
        <button>Create Project</button>
      )}
      
      {can('issue', 'assign') && (
        <button>Assign Task</button>
      )}
      
      {canManage && (
        <button>Manage Settings</button>
      )}
    </div>
  );
}

Check multiple permissions:

// Check if any permission is satisfied
const hasAnyPermission = canAny([
  { resource: 'project', action: 'create' },
  { resource: 'project', action: 'update' },
]);

// Check if all permissions are satisfied
const hasAllPermissions = canAll([
  { resource: 'member', action: 'create' },
  { resource: 'member', action: 'delete' },
]);

Server-side Permission Checks

Check permissions in tRPC routes or API routes:

import { protectedProcedure } from '~/lib/trpc';
import { hasPermission } from '~/lib/auth/permissions';
import { TRPCError } from '@trpc/server';

export const projectsRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ name: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const member = await ctx.auth.organization.getActiveMember();
      const role = member?.role;

      // Check permission
      if (!hasPermission(role as Role, 'project', 'create')) {
        throw new TRPCError({
          code: 'FORBIDDEN',
          message: 'No permission to create projects',
        });
      }

      // Create project
      const project = await ctx.db.project.create({
        data: {
          name: input.name,
          organizationId: member.organizationId,
        },
      });

      return project;
    }),
});

Workspace Settings

Open Workspace Settings

Method 1: Click the "Settings" button in the sidebar footer

Method 2: Trigger settings modal in code

'use client';

import { SettingsModal } from '~/components/dashboard/settings-modal';
import { useState } from 'react';

export function OpenSpaceSettings() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Workspace Settings
      </button>
      <SettingsModal
        open={isOpen}
        onOpenChange={setIsOpen}
        defaultSection="space"
        defaultTab="general"
      />
    </>
  );
}

General Settings

In the "General" tab, you can:

  • View and edit workspace name
  • View and edit workspace slug (for URL)
  • View creation time and member count
  • Delete workspace (only visible to owners)

Billing Settings

In the "Billing" tab, you can:

  • View current plan and usage
  • Upgrade or downgrade plan
  • Manage payment methods
  • View billing history

Permission Note: Only workspace owners can modify billing information; admins can only view.


Best Practices

✅ Do

  1. Clear Role Assignment

    • Only give Owner role to few people (usually 1-2)
    • Give Admin role to those who need management permissions
    • Most members should use Member role
    • External collaborators should use Viewer role
  2. Use Teams to Organize Members

    • Create teams by department or project
    • Use teams for permission grouping
    • Easier batch member management
  3. Regular Member Review

    • Periodically check member list
    • Remove inactive members
    • Update member roles to match actual responsibilities
  4. Secure Offboarding Process

    • Immediately remove access when employees leave
    • Check and cancel their created invitations
    • Change API keys if necessary

❌ Don't

  1. Don't Abuse Owner Role

    • Owner has permission to delete workspace
    • Should only be assigned to those who truly need it
  2. Don't Ignore Pending Invitations

    • Regularly clean up expired or invalid invitations
    • Avoid security risks
  3. Don't Delete Workspace Without Backup

    • Deletion is irreversible
    • Ensure important data is backed up

FAQ

How to Transfer Workspace Ownership?

Currently requires manual transfer through these steps:

  1. Set new owner as Admin
  2. Original and new owner coordinate transfer
  3. Contact technical support if help is needed

Is There a Limit on Member Count?

Depends on your plan:

  • Free: Up to 3 members
  • Pro: Up to 10 members
  • Enterprise: Unlimited

How to Bulk Invite Members?

Currently only supports individual invitations. Bulk invitation feature is in development.

Can I Rejoin After Leaving a Workspace?

Yes, but you need to receive a new invitation. Leaving doesn't delete your previous contribution records.


Troubleshooting

Cannot Invite Members

Possible Causes:

  1. You don't have invite permissions (need Admin or Owner)
  2. Email format is incorrect
  3. Reached plan member limit
  4. User is already a member

Solutions:

  • Check your role permissions
  • Verify email format
  • Upgrade plan or remove inactive members

Member List Not Displaying

Possible Causes:

  1. Network issues
  2. Insufficient permissions
  3. Data loading error

Solutions:

  • Refresh page
  • Check console errors
  • Contact technical support

Cannot Delete Workspace

Possible Causes:

  1. You're not the Owner
  2. Entered workspace name doesn't match

Solutions:

  • Confirm you have Owner role
  • Accurately enter workspace name (case-sensitive)

Next Steps

📚 More Resources:

On this page