feat: whyrating - initial project from turbostarter boilerplate

This commit is contained in:
Alejandro Gutiérrez
2026-02-04 01:54:52 +01:00
commit 5cdc07cd39
1618 changed files with 338230 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
---
title: Active organization
description: Set and switch the current organization context within your application.
url: /docs/mobile/organizations/active-organization
---
# Active organization
The active organization on mobile mirrors the behavior used on the [web app](/docs/web/organizations/active-organization) and in the [extension](/docs/extension/organizations). It is tracked in the authenticated session as `activeOrganizationId` and used to scope all organization-bound data and actions.
Below you can find how to read and work with the active organization in your mobile app context.
## Reading the active organization
Use your auth client's helper to read the active organization from the session. This keeps the client in sync with the server and avoids duplicating tenancy logic.
```tsx title="organizations.tsx"
import { authClient } from "~/lib/auth";
export function OrganizationsScreen() {
const organization = authClient.useActiveOrganization();
const member = authClient.useActiveMember();
return (
<>
<Text>{organization?.name}</Text>
<Text>{member?.role}</Text>
</>
);
}
```
This mirrors the [extension](/docs/extension/organizations) approach and the [web hook](/docs/web/organizations/active-organization), ensuring the active organization and member role stay consistent with the server session.
## Performing actions
When invoking API routes from the mobile app, prefer passing the `organizationId` explicitly with the payload. This guarantees the correct tenant is targeted even if multiple devices or views are active simultaneously.
```tsx title="create-post.tsx"
import { api } from "~/lib/api";
export function CreatePost() {
const activeOrganization = authClient.useActiveOrganization();
const { mutate } = useMutation({
mutationFn: async (post: PostInput) =>
api.posts.$post({
...post,
organizationId: activeOrganization?.id,
}),
});
return (
<Form>
<Button onPress={onSubmit(mutate)}>Submit</Button>
</Form>
);
}
```
This mirrors the recommendation from the [web guide](/docs/web/organizations/active-organization#api-route) and avoids edge cases tied to stale session values.
## Switching organizations
TurboStarter ships an account switcher out of the box for mobile. You can drop it into your app and customize labels and styling as needed.
```tsx title="settings.tsx"
import { AccountSwitcher } from "~/modules/organization/account-switcher";
export function SettingsScreen() {
return <AccountSwitcher />;
}
```
When a user selects a new organization, it calls your backend to update the session's `activeOrganizationId` and then re-read the session or invalidate related queries.
For deeper background on how the active organization is resolved, see the [web guide](/docs/web/organizations/active-organization).

View File

@@ -0,0 +1,58 @@
---
title: Invitations
description: Send, track, and accept organization invites.
url: /docs/mobile/organizations/invitations
---
# Invitations
Invite teammates by email to join an organization directly from your mobile app. Acceptance is straightforward: we verify the invite, create or reuse the membership with the intended role, and set the user's active organization.
The implementation uses the same APIs and rules as the [web app](/docs/web/organizations/invitations) and is powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization).
![Mobile invitations list](/images/docs/mobile/organizations/invitations/list.png)
## Capabilities
* Send invitations by email.
* View and filter invitations by status or role, and search by email.
* Resend or revoke an invitation.
* Accept an invitation via a [deep link](https://docs.expo.dev/linking/into-your-app/).
<Callout>
Permissions are enforced by roles. Typically, only organization admins can
send or manage invites. See [RBAC (Roles &
Permissions)](/docs/mobile/organizations/rbac).
</Callout>
## Inviting members
Sending an invitation typically requires the invitee's email and the intended role. You can add multiple recipients in the invitation form to invite several members at once.
![Invite members bottom sheet](/images/docs/mobile/organizations/invitations/invite.png)
After sending, the invitee receives an email with a link to accept. It's a [deep link](https://docs.expo.dev/guides/linking) that opens your app and automatically validates the invite.
## Handling invitations
When a recipient opens an invite link on their device, the app automatically handles the entire flow - reading, parsing, and validating the invite - for you.
![Join organization prompt](/images/docs/mobile/organizations/invitations/join.png)
When the user accepts, we create or reuse their membership and set the active organization in their session. If they reject the invite, we redirect them to their account home.
## Learn more
For underlying details shared across platforms, see the web documentation:
<Cards>
<Card title="Data model" description="Schema for organizations and invitations" href="/docs/web/organizations/data-model" />
<Card title="Statuses and flow" description="Invitation status codes and how they update" href="/docs/web/organizations/invitations#status" />
<Card title="Automatic invalidation" description="How invitations are automatically cleaned up" href="/docs/web/organizations/invitations#automatic-invalidation" />
<Card title="Admin management" description="Admin tooling for managing invitations" href="/docs/web/organizations/invitations#invitation-management" />
</Cards>
These cover the schema, token lifecycle, and admin tooling shared by the mobile and web apps.

View File

@@ -0,0 +1,77 @@
---
title: Overview
description: Learn how to use organizations/teams/multi-tenancy in TurboStarter mobile app.
url: /docs/mobile/organizations/overview
---
# Overview
Organizations let you build teams and multi-tenant SaaS out of the box in the mobile app.
Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces — all from iOS/Android — with the same secure data isolation used on the [web app](/docs/web/organizations/overview).
<Callout title="What is multi-tenancy?">
[Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration.
</Callout>
The feature is powered by the same [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and shares TurboStarter's API, routing, and data layer with the [web app](/docs/web/organizations/overview) and [extension](/docs/extension/organizations). That means your mobile app benefits from the same tenancy rules, RBAC checks, and invitations flow without duplicating backend logic.
<ThemedImage light="/images/docs/web/organizations/multi-tenancy/light.png" dark="/images/docs/web/organizations/multi-tenancy/dark.png" alt="Architecture" width={1375} zoomable height={955} />
## Architecture
On mobile, TurboStarter uses the same pragmatic multi-tenant architecture as the [web app](/docs/web/organizations/overview):
* **Tenant context** lives in the authenticated session as the active organization ID. The mobile client reads this context from the API and includes it when making requests.
* **Data scoping** is performed server-side via `organizationId` on tenant-owned tables and guard clauses in queries. Mobile screens consume scoped endpoints so users only see data for their selected organization.
* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”.
* **Extensibility**: add new tenant-bound entities by including `organizationId` in your schema and using the provided helpers to read or switch the active organization in the app.
This keeps data isolated per organization while remaining simple to reason about across platforms.
<Callout>
For deeper details on the shared data model used by the mobile app, see [Data
model](/docs/web/organizations/data-model).
</Callout>
## Concepts
The same core concepts apply in the mobile app:
| Concept | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. |
| **Member** | A user assigned to an organization. |
| **Role** | Access level within an organization (see [RBAC](/docs/mobile/organizations/rbac)). |
| **Invitation** | Email request to join an organization (see [Invitations](/docs/mobile/organizations/invitations)). |
| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. |
These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications on mobile.
## Development data
In development, TurboStarter automatically [seeds](/docs/mobile/installation/commands#seeding-database) example data when you set up services. The mobile app connects to the same development API, so you can test the full organizations flow end-to-end:
* One organization is created by default.
* All default roles are created and assigned within that organization.
* Sample invitations are generated so you can test the invite flow.
You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features — [reset](/docs/mobile/installation/commands#resetting-database) or [reseed](/docs/mobile/installation/commands#seeding-database) anytime to return to the default state.
The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables.
<Callout type="error" title="Never run in production">
The default development data and setup are intended for local development and
testing only. **Never** use these seeds or configurations in a production
environment - they are insecure and may expose sensitive functionality.
</Callout>
## Customization
You have flexibility to adapt organizations to fit your mobile experience. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the app copy accordingly.
You can adjust the available [roles and permissions](/docs/mobile/organizations/rbac) to suit your access model.
The [invitation flow](/docs/mobile/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work.
Feel free to check how to configure all of these features inside mobile application in the dedicated sections linked above.

View File

@@ -0,0 +1,115 @@
---
title: RBAC (Roles & Permissions)
description: Manage roles, permissions, and access scopes.
url: /docs/mobile/organizations/rbac
---
# RBAC (Roles & Permissions)
Role-based access control (RBAC) lets you define who can do what in an organization.
<Callout title="New to RBAC?">
If you're new to the RBAC concept, a simple mental model is:
* Users belong to organizations.
* Users get roles.
* Roles map to permissions on resources.
</Callout>
In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting—roles, permissions, teams, and member management—while handling critical logic with our own code.
This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role.
<Callout title="Everything is configured out of the box!">
TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs.
On mobile, use conditional UI (disable or hide actions) together with client helpers to match each member's role.
</Callout>
## Roles
Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles:
```ts
const MemberRole = {
MEMBER: "member",
ADMIN: "admin",
OWNER: "owner",
} as const;
```
A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application).
<Callout type="warn" title="Don't confuse organization admin with super admin">
The organization's `admin` role is different from the user's global `admin` role.
The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview).
</Callout>
To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details.
## Permissions
Permissions represent what actions a role can perform on which resources.
To check if the current user has permission to perform an action on mobile, use the client helper and handle the boolean result in your component logic.
```tsx title="create-project.tsx"
import { useQuery, useMutation } from "@tanstack/react-query";
import { authClient } from "~/lib/auth";
export function CreateProject() {
const { data: canCreate } = useQuery({
queryKey: ["permission", "project", "create"],
queryFn: () =>
authClient.organization.hasPermission({
permissions: { project: ["create"] },
}),
});
const { mutate, isPending } = useMutation({
mutationFn: async () => {
// perform the create action
},
});
return (
<Button
disabled={canCreate === false}
loading={isPending}
onPress={() => canCreate && mutate()}
>
Create
</Button>
);
}
```
When you already have the active member's role, prefer the client-side `checkRolePermission` to avoid extra API calls.
```tsx title="update-project.tsx"
import { authClient } from "~/lib/auth";
export function UpdateProject() {
const activeMember = authClient.useActiveMember();
const canUpdate = authClient.organization.checkRolePermission({
permission: {
project: ["update"],
},
role: activeMember.role,
});
return <Button disabled={!canUpdate}>Update</Button>;
}
```
We leverage the existing hook to retrieve the active member role within the [active organization](/docs/mobile/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip.
<Callout type="warn">
This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions.
</Callout>
If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API.
For further customization—such as dynamic access control, lifecycle hooks, or team management—see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization) and the [web guide](/docs/web/organizations/rbac).