@startsimpli/auth 0.4.17 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/auth",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "description": "Shared authentication package for StartSimpli Next.js apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,45 @@
1
+ /** @vitest-environment jsdom */
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { renderHook, waitFor } from '@testing-library/react';
4
+ import { useMembershipFromApi } from '../use-membership-from-api';
5
+
6
+ vi.mock('../../client/use-auth', () => ({
7
+ useAuth: () => ({
8
+ user: { id: 'u1', email: 'a@x.com', currentCompanyId: 'c1' },
9
+ isLoading: false,
10
+ isAuthenticated: true,
11
+ }),
12
+ }));
13
+
14
+ describe('useMembershipFromApi', () => {
15
+ it('wires myTeams + retrieve + companies.retrieve through to useMembership', async () => {
16
+ const api = {
17
+ teams: {
18
+ myTeams: vi.fn().mockResolvedValue([
19
+ {
20
+ id: 'm1',
21
+ userId: 'u1',
22
+ teamId: 't1',
23
+ role: 'owner',
24
+ joinedAt: '2025-01-01',
25
+ team: { id: 't1', slug: 'core', name: 'Core', companyId: 'c1' },
26
+ },
27
+ ]),
28
+ retrieve: vi.fn(),
29
+ },
30
+ companies: {
31
+ retrieve: vi.fn().mockResolvedValue({ id: 'c1', slug: 'acme', name: 'Acme' }),
32
+ },
33
+ };
34
+
35
+ const { result } = renderHook(() => useMembershipFromApi(api));
36
+
37
+ await waitFor(() => {
38
+ expect(result.current.company?.name).toBe('Acme');
39
+ expect(result.current.currentTeam?.slug).toBe('core');
40
+ expect(result.current.isOwner).toBe(true);
41
+ });
42
+ expect(api.teams.myTeams).toHaveBeenCalledTimes(1);
43
+ expect(api.companies.retrieve).toHaveBeenCalledWith('c1');
44
+ });
45
+ });
@@ -16,6 +16,11 @@ export {
16
16
  type MembershipRole,
17
17
  } from './use-membership';
18
18
 
19
+ export {
20
+ useMembershipFromApi,
21
+ type UseMembershipFromApiClient,
22
+ } from './use-membership-from-api';
23
+
19
24
  export {
20
25
  useInvitations,
21
26
  type UseInvitationsOptions,
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * useMembershipFromApi — thin adapter around `useMembership` that takes
5
+ * the @startsimpli/api `api` client and wires `fetchMyTeams`, `fetchTeam`,
6
+ * and `fetchCompany` automatically.
7
+ *
8
+ * Apps previously hand-rolled this adapter inside each /settings/team page;
9
+ * lifting it here means a one-liner adoption for the next consumer. The api
10
+ * argument is structurally typed (just the slice we use) so this hook can
11
+ * still live in @startsimpli/auth without depending on @startsimpli/api. startsim-o7s.
12
+ */
13
+
14
+ import { useMemo } from 'react';
15
+ import {
16
+ useMembership,
17
+ type MembershipRow,
18
+ type MembershipTeam,
19
+ type MembershipCompany,
20
+ type UseMembershipReturn,
21
+ } from './use-membership';
22
+
23
+ /** Minimum API surface used by the adapter. */
24
+ export interface UseMembershipFromApiClient {
25
+ teams: {
26
+ myTeams: () => Promise<
27
+ Array<{
28
+ id: string;
29
+ userId: string;
30
+ teamId: string;
31
+ role: MembershipRow['role'];
32
+ joinedAt: string;
33
+ team?: {
34
+ id: string;
35
+ slug: string;
36
+ name: string;
37
+ companyId: string;
38
+ };
39
+ }>
40
+ >;
41
+ retrieve: (idOrSlug: string) => Promise<{
42
+ id: string;
43
+ slug: string;
44
+ name: string;
45
+ companyId: string;
46
+ }>;
47
+ };
48
+ companies: {
49
+ retrieve: (idOrSlug: string) => Promise<{
50
+ id: string;
51
+ slug: string;
52
+ name: string;
53
+ }>;
54
+ };
55
+ }
56
+
57
+ export function useMembershipFromApi(
58
+ api: UseMembershipFromApiClient,
59
+ ): UseMembershipReturn {
60
+ const fetchMyTeams = useMemo(
61
+ () => async (): Promise<MembershipRow[]> => {
62
+ const rows = await api.teams.myTeams();
63
+ return rows.map((r) => ({
64
+ id: r.id,
65
+ userId: r.userId,
66
+ teamId: r.teamId,
67
+ role: r.role,
68
+ joinedAt: r.joinedAt,
69
+ team: r.team
70
+ ? {
71
+ id: r.team.id,
72
+ slug: r.team.slug,
73
+ name: r.team.name,
74
+ companyId: r.team.companyId,
75
+ }
76
+ : undefined,
77
+ }));
78
+ },
79
+ [api],
80
+ );
81
+
82
+ const fetchTeam = useMemo(
83
+ () => async (idOrSlug: string): Promise<MembershipTeam> => {
84
+ const t = await api.teams.retrieve(idOrSlug);
85
+ return { id: t.id, slug: t.slug, name: t.name, companyId: t.companyId };
86
+ },
87
+ [api],
88
+ );
89
+
90
+ const fetchCompany = useMemo(
91
+ () => async (idOrSlug: string): Promise<MembershipCompany> => {
92
+ const c = await api.companies.retrieve(idOrSlug);
93
+ return { id: c.id, slug: c.slug, name: c.name };
94
+ },
95
+ [api],
96
+ );
97
+
98
+ return useMembership({ fetchMyTeams, fetchTeam, fetchCompany });
99
+ }
package/src/index.ts CHANGED
@@ -62,6 +62,7 @@ export type {
62
62
  // useDomainClaims. Headless; caller passes the @startsimpli/api methods.
63
63
  export {
64
64
  useMembership,
65
+ useMembershipFromApi,
65
66
  useInvitations,
66
67
  useDomainClaims,
67
68
  } from './hooks';
@@ -72,6 +73,7 @@ export type {
72
73
  MembershipTeam,
73
74
  MembershipRow,
74
75
  MembershipRole,
76
+ UseMembershipFromApiClient,
75
77
  UseInvitationsOptions,
76
78
  UseInvitationsReturn,
77
79
  InvitationRow,