Skip to content

Commit 52c6497

Browse files
Switch to authClient.inviteMember for invitations
1 parent 508ac96 commit 52c6497

4 files changed

Lines changed: 33 additions & 79 deletions

File tree

apps/web/components/users-workspace.tsx

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ import {
3030
import type { AuthRole, CreateInvitationInput, CurrentUserDto } from '@acme/shared';
3131

3232
import { authClient } from '@/lib/auth-client';
33-
import { ApiClientError } from '@/lib/api-client';
34-
import { useCreateInvitationMutation, useUsersWorkspaceQuery } from '@/lib/queries';
33+
import { useUsersWorkspaceQuery } from '@/lib/queries';
3534

3635
const getErrorMessage = (error: unknown) =>
3736
error instanceof Error ? error.message : 'Unable to complete the request';
@@ -77,43 +76,47 @@ export function UsersWorkspace({
7776
const [notice, setNotice] = useState<string | null>(null);
7877
const [setupError, setSetupError] = useState<string | null>(null);
7978
const [isProvisioning, startProvisioning] = useTransition();
79+
const [isInviting, setIsInviting] = useState(false);
8080
const workspaceQuery = useUsersWorkspaceQuery();
81-
const createInvitationMutation = useCreateInvitationMutation();
8281

8382
const workspace = workspaceQuery.data;
8483
const effectiveViewer = workspace?.viewer ?? viewer;
8584
const members = workspace?.members ?? [];
8685
const invitations = workspace?.invitations ?? [];
8786
const canInviteMembers = canManageMembers(effectiveViewer.role);
88-
const errorMessage = createInvitationMutation.isError
89-
? getErrorMessage(createInvitationMutation.error)
90-
: workspaceQuery.isError
91-
? getErrorMessage(workspaceQuery.error)
92-
: null;
87+
const errorMessage = workspaceQuery.isError ? getErrorMessage(workspaceQuery.error) : null;
9388

9489
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
9590
event.preventDefault();
9691
setNotice(null);
92+
setSetupError(null);
9793
const submittedInvite = { ...inviteForm };
9894

9995
try {
100-
await createInvitationMutation.mutateAsync(submittedInvite);
96+
setIsInviting(true);
97+
const invitationResponse = (await authClient.organization.inviteMember({
98+
email: submittedInvite.email,
99+
role: submittedInvite.role,
100+
organizationId: effectiveViewer.organization?.id,
101+
resend: true,
102+
})) as {
103+
error?: {
104+
message?: string;
105+
} | null;
106+
};
107+
108+
if (invitationResponse.error) {
109+
setSetupError(invitationResponse.error.message ?? 'Unable to send invitation');
110+
return;
111+
}
112+
101113
setInviteForm({ email: '', role: 'member' });
102114
setNotice(`Invitation queued for ${submittedInvite.email}`);
115+
await workspaceQuery.refetch();
103116
} catch (error) {
104-
if (error instanceof ApiClientError && error.code === 'REQUEST_TIMEOUT') {
105-
const refreshedWorkspace = await workspaceQuery.refetch();
106-
const invitationWasCreated = refreshedWorkspace.data?.invitations.some(
107-
(invitation) => invitation.email === submittedInvite.email,
108-
);
109-
110-
if (invitationWasCreated) {
111-
setInviteForm({ email: '', role: 'member' });
112-
setNotice(
113-
`Invitation queued for ${submittedInvite.email}. Delivery took longer than the browser wait, but the pending invite was created successfully.`,
114-
);
115-
}
116-
}
117+
setSetupError(getErrorMessage(error));
118+
} finally {
119+
setIsInviting(false);
117120
}
118121
};
119122

@@ -270,11 +273,9 @@ export function UsersWorkspace({
270273
<Button
271274
type="submit"
272275
className="w-full"
273-
disabled={createInvitationMutation.isPending}
276+
disabled={isInviting}
274277
>
275-
{createInvitationMutation.isPending
276-
? 'Sending invitation...'
277-
: 'Send invitation'}
278+
{isInviting ? 'Sending invitation...' : 'Send invitation'}
278279
</Button>
279280
</form>
280281
{notice ? (
@@ -283,10 +284,10 @@ export function UsersWorkspace({
283284
<AlertDescription>{notice}</AlertDescription>
284285
</Alert>
285286
) : null}
286-
{errorMessage ? (
287+
{setupError ?? errorMessage ? (
287288
<Alert variant="destructive">
288289
<AlertTitle>Unable to send invitation</AlertTitle>
289-
<AlertDescription>{errorMessage}</AlertDescription>
290+
<AlertDescription>{setupError ?? errorMessage}</AlertDescription>
290291
</Alert>
291292
) : null}
292293
</>

apps/web/lib/api-client.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import {
2-
CreateInvitationInputSchema,
32
CurrentUserDtoSchema,
43
HealthDtoSchema,
54
UsersWorkspaceDtoSchema,
65
type ApiResponse,
7-
type CreateInvitationInput,
86
type CurrentUserDto,
97
type HealthDto,
108
type UsersWorkspaceDto,
119
} from '@acme/shared';
12-
import { z } from 'zod';
1310

1411
export class ApiClientError extends Error {
1512
constructor(
@@ -23,7 +20,6 @@ export class ApiClientError extends Error {
2320
}
2421

2522
const REQUEST_TIMEOUT_MS = 8_000;
26-
const INVITATION_REQUEST_TIMEOUT_MS = 20_000;
2723

2824
const parseApiResponse = async (response: Response): Promise<ApiResponse<unknown> | undefined> => {
2925
const text = await response.text();
@@ -42,7 +38,7 @@ const parseApiResponse = async (response: Response): Promise<ApiResponse<unknown
4238
const request = async <T>(
4339
path: string,
4440
init: RequestInit,
45-
schema: z.ZodType<T>,
41+
schema: { parse(data: unknown): T },
4642
options?: {
4743
timeoutMs?: number;
4844
timeoutMessage?: string;
@@ -102,20 +98,4 @@ export const apiClient = {
10298
getMe: () => request<CurrentUserDto>('/api/v1/me', { method: 'GET' }, CurrentUserDtoSchema),
10399
getUsersWorkspace: () =>
104100
request<UsersWorkspaceDto>('/api/v1/users', { method: 'GET' }, UsersWorkspaceDtoSchema),
105-
createInvitation: (input: CreateInvitationInput) =>
106-
request(
107-
'/api/v1/invitations',
108-
{
109-
method: 'POST',
110-
body: JSON.stringify(CreateInvitationInputSchema.parse(input)),
111-
},
112-
z.object({
113-
invitationId: z.uuid(),
114-
}),
115-
{
116-
timeoutMs: INVITATION_REQUEST_TIMEOUT_MS,
117-
timeoutMessage:
118-
'Invitation delivery is taking longer than expected. We will check whether the invite was still created before showing an error.',
119-
},
120-
),
121101
};

apps/web/lib/auth-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const authClient = createAuthClient({
2929
create: AsyncClientMethod;
3030
acceptInvitation: AsyncClientMethod;
3131
getInvitation: AsyncClientMethod;
32+
inviteMember: AsyncClientMethod;
3233
list: AsyncClientMethod;
3334
setActive: AsyncClientMethod;
3435
};

apps/web/lib/queries.ts

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
'use client';
22

3-
import {
4-
useMutation,
5-
useQuery,
6-
useQueryClient,
7-
type UseMutationResult,
8-
type UseQueryResult,
9-
} from '@tanstack/react-query';
3+
import { useQuery, type UseQueryResult } from '@tanstack/react-query';
104

11-
import type {
12-
CreateInvitationInput,
13-
CurrentUserDto,
14-
HealthDto,
15-
UsersWorkspaceDto,
16-
} from '@acme/shared';
5+
import type { CurrentUserDto, HealthDto, UsersWorkspaceDto } from '@acme/shared';
176

187
import { apiClient } from '@/lib/api-client';
198
import { queryKeys } from '@/lib/query-keys';
@@ -36,20 +25,3 @@ export const useUsersWorkspaceQuery = (): UseQueryResult<UsersWorkspaceDto> =>
3625
queryKey: queryKeys.users.workspace,
3726
queryFn: apiClient.getUsersWorkspace,
3827
});
39-
40-
export const useCreateInvitationMutation = (): UseMutationResult<
41-
{ invitationId: string },
42-
Error,
43-
CreateInvitationInput
44-
> => {
45-
const queryClient = useQueryClient();
46-
47-
return useMutation({
48-
mutationFn: apiClient.createInvitation,
49-
onSuccess() {
50-
void queryClient.invalidateQueries({
51-
queryKey: queryKeys.users.workspace,
52-
});
53-
},
54-
});
55-
};

0 commit comments

Comments
 (0)