# How Team Collaboration and Invitations Are Implemented in the Developer Roadmap App

> Discover how team collaboration and invitations work in the Developer Roadmap app. Learn about nanostores, React components, and REST endpoints powering this feature.

- Repository: [Kamran Ahmed/developer-roadmap](https://github.com/kamranahmedse/developer-roadmap)
- Tags: internals
- Published: 2026-02-24

---

**The team collaboration feature uses nanostores for reactive state management, React components for UI flows, and REST-style endpoints for creating, fetching, and responding to invitations.**

The `kamranahmedse/developer-roadmap` repository implements a complete team collaboration system that allows users to create teams, invite members via email, and manage roles through a permission-based interface. This implementation spans client-side state management, React component architecture, and HTTP API integration to deliver a seamless invitation lifecycle from creation to acceptance.

## State Management with Nanostores

The foundation of the team collaboration feature lives in [`src/stores/team.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/stores/team.ts), which uses **nanostores** to provide reactive state across the application. The store defines several key atoms that track the user's team memberships and permissions.

```typescript
export const $teamList = atom<UserTeamItem[]>([]);
export const $currentTeam = atom<UserTeamItem | undefined>();
export const $currentTeamRole = computed($currentTeam, t => t?.role);
export const $isCurrentTeamAdmin = computed($currentTeamRole, r => ['admin'].includes(r!));
export const $canManageCurrentTeam = computed($currentTeamRole, r => ['admin','manager'].includes(r!));

```

These atoms enable any component to subscribe to team data and role permissions without prop drilling. The `$teamList` atom holds all teams where the user has membership, while computed atoms like `$isCurrentTeamAdmin` and `$canManageCurrentTeam` derive boolean permissions used to conditionally render invitation controls.

## Creating and Managing Teams

Team creation begins in [`src/components/CreateTeam/CreateTeamForm.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/components/CreateTeam/CreateTeamForm.tsx), which handles the initial team document creation. After a successful creation, the component fetches the new team details to populate the store.

```typescript
const { response, error } = await httpGet<TeamDocument>(`${API}/v1-get-team/${teamId}`);

```

Once a team exists in `$teamList`, the invitation functionality becomes available to users with appropriate permissions. The [`TeamsList.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/TeamsList.tsx) component reads from `$teamList` to display teams and filters entries based on `team.status` to show pending invitations separately from active memberships.

## The Invitation Flow

The invitation system follows a complete lifecycle through distinct API endpoints, handled by specialized React components that manage each phase of the process.

### Sending Invitations

The [`InviteMemberPopup.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/InviteMemberPopup.tsx) component manages the invitation creation interface. It collects an email address and role selection (via [`RoleDropdown.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/RoleDropdown.tsx)), then submits the invitation through a POST request.

```typescript
const { response, error } = await httpPost(
  `${import.meta.env.PUBLIC_API_URL}/v1-invite-member/${teamId}`,
  { email, role: selectedRole },
);

```

The request flows through the generic `httpPost` wrapper in [`src/lib/http.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/http.ts), which automatically injects authentication tokens and fingerprint headers. The backend creates an invitation record and emails a link formatted as `/respond-invite?i=<inviteId>` to the recipient.

### Responding to Invitations

When an invitee clicks the email link, [`RespondInviteForm.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/RespondInviteForm.tsx) handles the acceptance or rejection workflow. The component first extracts the invitation ID from the URL parameters.

```typescript
const { i: inviteId } = getUrlParams();

```

It then fetches invitation details using a GET request to validate the invitation before displaying action buttons.

```typescript
const { response, error } = await httpGet<InvitationResponse>(
  `${import.meta.env.PUBLIC_API_URL}/v1-get-invitation/${inviteId}`,
);

```

Upon user action, the component sends a PATCH request to record the decision.

```typescript
const { response, error } = await httpPatch<{ teamId: string }>(
  `${import.meta.env.PUBLIC_API_URL}/v1-respond-invite/${inviteId}`,
  { status: 'accept' | 'reject' },
);

```

Successful acceptance redirects the user to `/teams/${teamId}/activity`, while rejection routes to the home page. The backend updates the member's status from `invited` to `joined`, which reflects in the client's `$teamList` on the next data fetch.

## Permission-Based UI Rendering

Components throughout the application use the nanostore computed atoms to gate invitation functionality. In [`DashboardPage.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/DashboardPage.tsx) and team list views, the system checks `$isCurrentTeamAdmin` or `$canManageCurrentTeam` before rendering invite buttons or management controls.

```typescript
import { $currentTeam, $isCurrentTeamAdmin } from '@/stores/team';

const isAdmin = $currentTeam.get()?._id === team._id && $isCurrentTeamAdmin.get();

```

Only users with `admin` or `manager` roles see the invitation interface, ensuring proper access control without additional server round-trips for permission checks.

## Summary

- **State management** relies on nanostores in [`src/stores/team.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/stores/team.ts) to track `$teamList`, `$currentTeam`, and computed role permissions like `$isCurrentTeamAdmin`.
- **Team creation** occurs through [`CreateTeamForm.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/CreateTeamForm.tsx), which validates the new team via `httpGet` before enabling invitation features.
- **Invitations** are sent via `POST /v1-invite-member/:teamId` from [`InviteMemberPopup.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/InviteMemberPopup.tsx), with the backend generating email links containing unique invitation IDs.
- **Invitation responses** are handled by [`RespondInviteForm.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/RespondInviteForm.tsx) using `GET /v1-get-invitation/:inviteId` to fetch details and `PATCH /v1-respond-invite/:inviteId` to accept or reject.
- **Permission checks** use reactive atoms to conditionally render UI elements based on the user's role (admin, manager, or member) in the current team context.

## Frequently Asked Questions

### How does the application track which users can invite new team members?

The application uses computed atoms in [`src/stores/team.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/stores/team.ts) called `$isCurrentTeamAdmin` and `$canManageCurrentTeam`. These check if the user's role in `$currentTeam` is `admin` or `manager`. React components subscribe to these atoms and only render invitation buttons when the user has the required permissions, eliminating unnecessary API calls for unauthorized actions.

### What API endpoints handle the invitation lifecycle?

The system uses three main REST-style endpoints: `POST /v1-invite-member/:teamId` to create invitations, `GET /v1-get-invitation/:inviteId` to fetch invitation details when a user clicks an email link, and `PATCH /v1-respond-invite/:inviteId` to process acceptance or rejection responses. All requests flow through the generic HTTP wrappers in [`src/lib/http.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/http.ts) which handle authentication and error formatting.

### How does the invitee's team status update from "invited" to "joined"?

When an invitee accepts via [`RespondInviteForm.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/RespondInviteForm.tsx), the `PATCH` request updates the server-side status. The client-side `$teamList` atom refreshes on the next data fetch (either through automatic sync or manual refresh), replacing the `status: 'invited'` entry with `status: 'joined'` or removing rejected invitations entirely. The [`TeamsList.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/TeamsList.tsx) component reacts to these state changes to update the UI badges and navigation links.

### Can invitations be sent programmatically outside the React UI?

Yes, the same `httpPost` utility used in [`InviteMemberPopup.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/InviteMemberPopup.tsx) can be imported from [`src/lib/http.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/http.ts) and used in server-side scripts or service layers. You would call the `v1-invite-member` endpoint with the team ID, email, and role parameters, passing the same authentication headers required by the client implementation.