@shipfox/client-auth 12.0.2 → 13.0.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +32 -0
- package/dist/components/auth-guard.js +1 -1
- package/dist/components/auth-guard.js.map +1 -1
- package/dist/components/workspace-switcher.d.ts.map +1 -1
- package/dist/components/workspace-switcher.js +4 -2
- package/dist/components/workspace-switcher.js.map +1 -1
- package/dist/components/workspace-switcher.stories.d.ts.map +1 -1
- package/dist/components/workspace-switcher.stories.js +3 -2
- package/dist/components/workspace-switcher.stories.js.map +1 -1
- package/dist/core/auth.d.ts +16 -0
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js.map +1 -1
- package/dist/hooks/api/workspace-auth.d.ts +4 -1
- package/dist/hooks/api/workspace-auth.d.ts.map +1 -1
- package/dist/hooks/api/workspace-auth.js +45 -3
- package/dist/hooks/api/workspace-auth.js.map +1 -1
- package/dist/hooks/api/workspace-mapper.js +1 -0
- package/dist/hooks/api/workspace-mapper.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/pages/form-errors.d.ts +1 -1
- package/dist/pages/form-errors.d.ts.map +1 -1
- package/dist/pages/form-errors.js +7 -0
- package/dist/pages/form-errors.js.map +1 -1
- package/dist/pages/signup-page.d.ts.map +1 -1
- package/dist/pages/signup-page.js +79 -12
- package/dist/pages/signup-page.js.map +1 -1
- package/dist/pages/workspace-onboarding-page.d.ts.map +1 -1
- package/dist/pages/workspace-onboarding-page.js +81 -27
- package/dist/pages/workspace-onboarding-page.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/components/auth-guard.tsx +1 -1
- package/src/components/redirect-target.test.ts +7 -11
- package/src/components/workspace-switcher.stories.tsx +3 -2
- package/src/components/workspace-switcher.tsx +3 -1
- package/src/core/auth.ts +12 -0
- package/src/hooks/api/workspace-auth.test.ts +66 -3
- package/src/hooks/api/workspace-auth.ts +51 -4
- package/src/hooks/api/workspace-mapper.test.ts +2 -1
- package/src/hooks/api/workspace-mapper.ts +1 -1
- package/src/hooks/index.ts +6 -1
- package/src/pages/form-errors.test.ts +11 -1
- package/src/pages/form-errors.ts +6 -1
- package/src/pages/logout-page.test.tsx +2 -2
- package/src/pages/signup-page.test.tsx +78 -0
- package/src/pages/signup-page.tsx +92 -12
- package/src/pages/workspace-onboarding-page.test.tsx +123 -1
- package/src/pages/workspace-onboarding-page.tsx +60 -7
- package/src/redirect-context.test.ts +5 -7
- package/src/routes/index.tsx +4 -1
- package/src/routes/inputs.test.ts +2 -2
- package/test/pages.tsx +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { signupBodySchema } from '@shipfox/api-auth-dto';
|
|
3
|
-
import { AuthShell, useRouteSearch } from '@shipfox/client-shell/runtime';
|
|
3
|
+
import { AuthShell, rememberLastWorkspaceId, useRouteSearch, userWorkspacesQueryKey } from '@shipfox/client-shell/runtime';
|
|
4
4
|
import { displayNameFieldError } from '@shipfox/client-ui';
|
|
5
5
|
import { Button, ButtonLink } from '@shipfox/react-ui/button';
|
|
6
6
|
import { Callout } from '@shipfox/react-ui/callout';
|
|
@@ -9,6 +9,7 @@ import { Icon } from '@shipfox/react-ui/icon';
|
|
|
9
9
|
import { toast } from '@shipfox/react-ui/toast';
|
|
10
10
|
import { Text } from '@shipfox/react-ui/typography';
|
|
11
11
|
import { useForm } from '@tanstack/react-form';
|
|
12
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
12
13
|
import { Link, useNavigate } from '@tanstack/react-router';
|
|
13
14
|
import { useAtom } from 'jotai';
|
|
14
15
|
import { useEffect, useRef, useState } from 'react';
|
|
@@ -26,6 +27,7 @@ export function SignupPage() {
|
|
|
26
27
|
const verifyEmail = useVerifyEmailAuth();
|
|
27
28
|
const resendEmailVerification = useResendEmailVerificationAuth();
|
|
28
29
|
const refreshAuth = useRefreshAuth();
|
|
30
|
+
const queryClient = useQueryClient();
|
|
29
31
|
const navigate = useNavigate();
|
|
30
32
|
const search = useRouteSearch(validateRedirectSearch);
|
|
31
33
|
const invitationToken = extractInvitationToken(search.redirect);
|
|
@@ -36,11 +38,28 @@ export function SignupPage() {
|
|
|
36
38
|
const [nextResendAvailableAt, setNextResendAvailableAt] = useState();
|
|
37
39
|
const [formError, setFormError] = useState();
|
|
38
40
|
const [resendError, setResendError] = useState();
|
|
41
|
+
const [invitationRefreshFailure, setInvitationRefreshFailure] = useState();
|
|
42
|
+
const [isRetryingInvitationRefresh, setIsRetryingInvitationRefresh] = useState(false);
|
|
39
43
|
const draftRef = useRef(authFormDraft);
|
|
40
44
|
draftRef.current = authFormDraft;
|
|
41
45
|
// Set just before clearing the draft on success so the unmount cleanup
|
|
42
46
|
// below does not repersist the just-submitted credentials.
|
|
43
47
|
const skipDraftPersistRef = useRef(false);
|
|
48
|
+
async function refreshInvitationWorkspace(workspaceId) {
|
|
49
|
+
for(let attempt = 0; attempt < 2; attempt += 1){
|
|
50
|
+
try {
|
|
51
|
+
await refreshAuth();
|
|
52
|
+
const workspaces = queryClient.getQueryData(userWorkspacesQueryKey);
|
|
53
|
+
const memberships = workspaces?.memberships ?? [];
|
|
54
|
+
if (memberships.some(({ id })=>id === workspaceId)) return true;
|
|
55
|
+
} catch {
|
|
56
|
+
// A mount-time refresh can race the post-signup refresh. Retry once so
|
|
57
|
+
// an in-flight request that predates membership creation does not turn
|
|
58
|
+
// a successful invitation into a manual recovery flow.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
44
63
|
const form = useForm({
|
|
45
64
|
defaultValues: {
|
|
46
65
|
email: authFormDraft.email,
|
|
@@ -69,18 +88,21 @@ export function SignupPage() {
|
|
|
69
88
|
skipDraftPersistRef.current = true;
|
|
70
89
|
setAuthFormDraft(initialAuthFormDraft);
|
|
71
90
|
if (invitationToken && result.membership && invitationPending) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
const joinedWorkspace = await refreshInvitationWorkspace(result.membership.workspaceId);
|
|
92
|
+
if (!joinedWorkspace) {
|
|
93
|
+
setInvitationRefreshFailure({
|
|
94
|
+
workspaceId: result.membership.workspaceId,
|
|
95
|
+
workspaceName: invitationPending.workspaceName,
|
|
96
|
+
userId: result.user?.id
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
77
99
|
}
|
|
78
100
|
toast.success(`You joined ${invitationPending.workspaceName}.`);
|
|
101
|
+
if (result.user?.id) {
|
|
102
|
+
rememberLastWorkspaceId(result.user.id, result.membership.workspaceId);
|
|
103
|
+
}
|
|
79
104
|
await navigate({
|
|
80
|
-
to: '/
|
|
81
|
-
params: {
|
|
82
|
-
wid: result.membership.workspaceId
|
|
83
|
-
}
|
|
105
|
+
to: '/'
|
|
84
106
|
});
|
|
85
107
|
return;
|
|
86
108
|
}
|
|
@@ -119,6 +141,21 @@ export function SignupPage() {
|
|
|
119
141
|
}
|
|
120
142
|
}
|
|
121
143
|
});
|
|
144
|
+
async function retryInvitationAuthRefresh() {
|
|
145
|
+
if (!invitationRefreshFailure || isRetryingInvitationRefresh) return;
|
|
146
|
+
setIsRetryingInvitationRefresh(true);
|
|
147
|
+
const joinedWorkspace = await refreshInvitationWorkspace(invitationRefreshFailure.workspaceId);
|
|
148
|
+
setIsRetryingInvitationRefresh(false);
|
|
149
|
+
if (!joinedWorkspace) return;
|
|
150
|
+
if (invitationRefreshFailure.userId) {
|
|
151
|
+
rememberLastWorkspaceId(invitationRefreshFailure.userId, invitationRefreshFailure.workspaceId);
|
|
152
|
+
}
|
|
153
|
+
toast.success(`You joined ${invitationRefreshFailure.workspaceName}.`);
|
|
154
|
+
setInvitationRefreshFailure(undefined);
|
|
155
|
+
await navigate({
|
|
156
|
+
to: '/'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
122
159
|
// When arriving from an invitation link, prefill the email and lock it.
|
|
123
160
|
useEffect(()=>{
|
|
124
161
|
if (invitationPending && form.state.values.email !== invitationPending.email) {
|
|
@@ -133,8 +170,9 @@ export function SignupPage() {
|
|
|
133
170
|
form,
|
|
134
171
|
setAuthFormDraft
|
|
135
172
|
]);
|
|
136
|
-
// Sync form values back to the Jotai draft on unmount
|
|
137
|
-
//
|
|
173
|
+
// Sync form values back to the Jotai draft on unmount. The draft stores only
|
|
174
|
+
// email and password; name is intentionally not persisted across navigation.
|
|
175
|
+
// Skipped after a
|
|
138
176
|
// successful signup because we just intentionally cleared the draft.
|
|
139
177
|
useEffect(()=>{
|
|
140
178
|
return ()=>{
|
|
@@ -165,6 +203,35 @@ export function SignupPage() {
|
|
|
165
203
|
setResendError(authErrorMessage(error));
|
|
166
204
|
}
|
|
167
205
|
}
|
|
206
|
+
if (invitationRefreshFailure) {
|
|
207
|
+
return /*#__PURE__*/ _jsx(AuthShell, {
|
|
208
|
+
title: `Join ${invitationRefreshFailure.workspaceName}`,
|
|
209
|
+
description: "Your account was created, but we could not finish signing you in.",
|
|
210
|
+
children: /*#__PURE__*/ _jsx(Callout, {
|
|
211
|
+
role: "alert",
|
|
212
|
+
type: "error",
|
|
213
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
214
|
+
className: "flex flex-col gap-8",
|
|
215
|
+
children: [
|
|
216
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
217
|
+
size: "sm",
|
|
218
|
+
children: "Try again to finish joining your workspace. You can safely retry this step."
|
|
219
|
+
}),
|
|
220
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
221
|
+
type: "button",
|
|
222
|
+
variant: "secondary",
|
|
223
|
+
className: "w-fit",
|
|
224
|
+
isLoading: isRetryingInvitationRefresh,
|
|
225
|
+
onClick: ()=>{
|
|
226
|
+
void retryInvitationAuthRefresh();
|
|
227
|
+
},
|
|
228
|
+
children: "Retry sign-in"
|
|
229
|
+
})
|
|
230
|
+
]
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
}
|
|
168
235
|
if (emailChallenge) {
|
|
169
236
|
return /*#__PURE__*/ _jsx(EmailCodeVerification, {
|
|
170
237
|
destination: emailChallenge.email,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {AuthShell, useRouteSearch} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const navigate = useNavigate();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync({\n email: parsed.email,\n password: parsed.password,\n name: parsed.name,\n ...(parsed.invitation_token ? {invitationToken: parsed.invitation_token} : {}),\n });\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n try {\n await refreshAuth();\n } catch {\n // Refresh failures don't block the success message — the next API\n // call's 401 handling will re-route the user.\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n await navigate({\n to: '/workspaces/$wid',\n params: {wid: result.membership.workspaceId},\n });\n return;\n }\n\n if (invitationToken && result.acceptError) {\n toast.error(result.acceptError.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.emailChallenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.emailChallenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);\n } catch (error) {\n const mapped = signupErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n // When arriving from an invitation link, prefill the email and lock it.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync form values back to the Jotai draft on unmount (only email + password\n // — name is intentionally not persisted across navigation). Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n });\n setNextResendAvailableAt(result.nextResendAvailableAt);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) => displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Name\" id=\"name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"name\"\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"email\"\n validators={{\n onBlur: signupBodySchema.shape.email,\n onSubmit: signupBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: signupBodySchema.shape.password,\n onSubmit: signupBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={signup.isPending} type=\"submit\">\n {signup.isPending ? 'Creating account...' : 'Create account'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already have an account?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/login\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Log in\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["signupBodySchema","AuthShell","useRouteSearch","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","Link","useNavigate","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","navigate","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","draftRef","current","skipDraftPersistRef","form","defaultValues","email","password","name","onSubmit","value","undefined","parsed","parse","invitation_token","result","mutateAsync","membership","success","workspaceName","to","params","wid","workspaceId","acceptError","error","message","token","Error","user","id","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","state","values","setFieldValue","onResendVerificationEmail","isPending","challengeId","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","size","className","asChild","variant","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","title","description","noValidate","event","preventDefault","stopPropagation","handleSubmit","role","type","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden","isLoading"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/pages/signup-page.tsx"],"sourcesContent":["import {signupBodySchema} from '@shipfox/api-auth-dto';\nimport {\n AuthShell,\n rememberLastWorkspaceId,\n useRouteSearch,\n userWorkspacesQueryKey,\n} from '@shipfox/client-shell/runtime';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button, ButtonLink} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {Link, useNavigate} from '@tanstack/react-router';\nimport {useAtom} from 'jotai';\nimport {useEffect, useRef, useState} from 'react';\nimport {EmailCodeVerification} from '#/components/email-code-verification.js';\nimport {useRefreshAuth} from '#hooks/api/refresh-auth.js';\nimport {useSignupAuth} from '#hooks/api/signup-auth.js';\nimport {useResendEmailVerificationAuth, useVerifyEmailAuth} from '#hooks/api/verify-email-auth.js';\nimport {authFormDraftAtom, initialAuthFormDraft} from '#state/auth.js';\nimport {validateRedirectSearch} from '../routes/inputs.js';\nimport {signupErrorToFormError} from './form-errors.js';\nimport {authErrorMessage} from './form-utils.js';\nimport {\n extractInvitationToken,\n pendingInvitation,\n useInvitationContext,\n} from './invitation-context.js';\n\nexport function SignupPage() {\n const signup = useSignupAuth();\n const verifyEmail = useVerifyEmailAuth();\n const resendEmailVerification = useResendEmailVerificationAuth();\n const refreshAuth = useRefreshAuth();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const search = useRouteSearch(validateRedirectSearch);\n const invitationToken = extractInvitationToken(search.redirect);\n const invitationPreview = useInvitationContext(invitationToken);\n const invitationPending = pendingInvitation(invitationPreview.data);\n const [authFormDraft, setAuthFormDraft] = useAtom(authFormDraftAtom);\n const [emailChallenge, setEmailChallenge] = useState<{email: string; id: string} | undefined>();\n const [nextResendAvailableAt, setNextResendAvailableAt] = useState<string | undefined>();\n const [formError, setFormError] = useState<string | undefined>();\n const [resendError, setResendError] = useState<string | undefined>();\n const [invitationRefreshFailure, setInvitationRefreshFailure] = useState<{\n workspaceId: string;\n workspaceName: string;\n userId?: string | undefined;\n }>();\n const [isRetryingInvitationRefresh, setIsRetryingInvitationRefresh] = useState(false);\n const draftRef = useRef(authFormDraft);\n draftRef.current = authFormDraft;\n // Set just before clearing the draft on success so the unmount cleanup\n // below does not repersist the just-submitted credentials.\n const skipDraftPersistRef = useRef(false);\n\n async function refreshInvitationWorkspace(workspaceId: string) {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n await refreshAuth();\n const workspaces = queryClient.getQueryData<{\n memberships: Array<{id: string}>;\n }>(userWorkspacesQueryKey);\n const memberships = workspaces?.memberships ?? [];\n if (memberships.some(({id}) => id === workspaceId)) return true;\n } catch {\n // A mount-time refresh can race the post-signup refresh. Retry once so\n // an in-flight request that predates membership creation does not turn\n // a successful invitation into a manual recovery flow.\n }\n }\n return false;\n }\n\n const form = useForm({\n defaultValues: {email: authFormDraft.email, password: authFormDraft.password, name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const parsed = signupBodySchema.parse({\n email: value.email,\n password: value.password,\n name: value.name,\n ...(invitationToken ? {invitation_token: invitationToken} : {}),\n });\n const result = await signup.mutateAsync({\n email: parsed.email,\n password: parsed.password,\n name: parsed.name,\n ...(parsed.invitation_token ? {invitationToken: parsed.invitation_token} : {}),\n });\n skipDraftPersistRef.current = true;\n setAuthFormDraft(initialAuthFormDraft);\n\n if (invitationToken && result.membership && invitationPending) {\n const joinedWorkspace = await refreshInvitationWorkspace(result.membership.workspaceId);\n if (!joinedWorkspace) {\n setInvitationRefreshFailure({\n workspaceId: result.membership.workspaceId,\n workspaceName: invitationPending.workspaceName,\n userId: result.user?.id,\n });\n return;\n }\n toast.success(`You joined ${invitationPending.workspaceName}.`);\n if (result.user?.id) {\n rememberLastWorkspaceId(result.user.id, result.membership.workspaceId);\n }\n await navigate({to: '/'});\n return;\n }\n\n if (invitationToken && result.acceptError) {\n toast.error(result.acceptError.message);\n await navigate({\n to: '/invitations/accept',\n search: {token: invitationToken},\n });\n return;\n }\n\n if (!result.emailChallenge) {\n throw new Error('Signup did not return an email verification challenge');\n }\n setEmailChallenge({email: result.user.email, id: result.emailChallenge.id});\n setResendError(undefined);\n setNextResendAvailableAt(result.emailChallenge.nextResendAvailableAt);\n } catch (error) {\n const mapped = signupErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n async function retryInvitationAuthRefresh() {\n if (!invitationRefreshFailure || isRetryingInvitationRefresh) return;\n setIsRetryingInvitationRefresh(true);\n const joinedWorkspace = await refreshInvitationWorkspace(invitationRefreshFailure.workspaceId);\n setIsRetryingInvitationRefresh(false);\n if (!joinedWorkspace) return;\n if (invitationRefreshFailure.userId) {\n rememberLastWorkspaceId(\n invitationRefreshFailure.userId,\n invitationRefreshFailure.workspaceId,\n );\n }\n toast.success(`You joined ${invitationRefreshFailure.workspaceName}.`);\n setInvitationRefreshFailure(undefined);\n await navigate({to: '/'});\n }\n\n // When arriving from an invitation link, prefill the email and lock it.\n useEffect(() => {\n if (invitationPending && form.state.values.email !== invitationPending.email) {\n form.setFieldValue('email', invitationPending.email);\n setAuthFormDraft((current) => ({...current, email: invitationPending.email}));\n }\n }, [invitationPending, form, setAuthFormDraft]);\n\n // Sync form values back to the Jotai draft on unmount. The draft stores only\n // email and password; name is intentionally not persisted across navigation.\n // Skipped after a\n // successful signup because we just intentionally cleared the draft.\n useEffect(() => {\n return () => {\n if (skipDraftPersistRef.current) return;\n const {email, password} = form.state.values;\n if (email !== draftRef.current.email || password !== draftRef.current.password) {\n setAuthFormDraft({email, password});\n }\n };\n }, [form, setAuthFormDraft]);\n\n async function onResendVerificationEmail() {\n if (!emailChallenge || resendEmailVerification.isPending) return;\n\n setResendError(undefined);\n try {\n const result = await resendEmailVerification.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n });\n setNextResendAvailableAt(result.nextResendAvailableAt);\n toast.success('If another verification email can be sent, it will arrive shortly.');\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }\n\n if (invitationRefreshFailure) {\n return (\n <AuthShell\n title={`Join ${invitationRefreshFailure.workspaceName}`}\n description=\"Your account was created, but we could not finish signing you in.\"\n >\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex flex-col gap-8\">\n <Text size=\"sm\">\n Try again to finish joining your workspace. You can safely retry this step.\n </Text>\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"w-fit\"\n isLoading={isRetryingInvitationRefresh}\n onClick={() => {\n void retryInvitationAuthRefresh();\n }}\n >\n Retry sign-in\n </Button>\n </div>\n </Callout>\n </AuthShell>\n );\n }\n\n if (emailChallenge) {\n return (\n <EmailCodeVerification\n destination={emailChallenge.email}\n nextResendAvailableAt={nextResendAvailableAt}\n isResending={resendEmailVerification.isPending}\n isVerifying={verifyEmail.isPending}\n error={resendError}\n onVerify={async (code) => {\n setResendError(undefined);\n try {\n await verifyEmail.mutateAsync({\n email: emailChallenge.email,\n challengeId: emailChallenge.id,\n code,\n });\n await refreshAuth();\n toast.success('Your email is verified. You are now logged in.');\n await navigate({to: '/', replace: true});\n } catch (error) {\n setResendError(authErrorMessage(error));\n }\n }}\n onResend={onResendVerificationEmail}\n onUseAnotherEmail={() => {\n setEmailChallenge(undefined);\n setResendError(undefined);\n }}\n >\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already verified?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link to=\"/auth/login\">Log in</Link>\n </ButtonLink>\n </Text>\n </EmailCodeVerification>\n );\n }\n\n const headerTitle = invitationPending\n ? `Join ${invitationPending.workspaceName}`\n : 'Create your Shipfox account';\n const headerDescription = invitationPending\n ? `Create an account to accept your invitation.`\n : 'Start with your email and a password.';\n const isInvitationEmailLocked = Boolean(invitationPending);\n const invitationRedirect = invitationToken\n ? `/invitations/accept?token=${encodeURIComponent(invitationToken)}`\n : undefined;\n\n function persistDraft() {\n const {email, password} = form.state.values;\n setAuthFormDraft({email, password});\n }\n\n return (\n <AuthShell title={headerTitle} description={headerDescription}>\n <form\n className=\"flex flex-col gap-18\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) => displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Name', signupBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Name\" id=\"name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"name\"\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"email\"\n validators={{\n onBlur: signupBodySchema.shape.email,\n onSubmit: signupBodySchema.shape.email,\n }}\n >\n {(field) => (\n <FormField label=\"Email\" id=\"email\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"email\"\n name=\"email\"\n type=\"email\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n readOnly={isInvitationEmailLocked}\n iconRight={\n isInvitationEmailLocked ? (\n <Icon\n aria-hidden=\"true\"\n className=\"size-16 text-foreground-neutral-disabled\"\n name=\"lockLine\"\n />\n ) : undefined\n }\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"password\"\n validators={{\n onBlur: signupBodySchema.shape.password,\n onSubmit: signupBodySchema.shape.password,\n }}\n >\n {(field) => (\n <FormField label=\"Password\" id=\"password\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"new-password\"\n name=\"password\"\n type=\"password\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => {\n field.handleBlur();\n persistDraft();\n }}\n />\n </FormField>\n )}\n </form.Field>\n <Button className=\"w-full\" isLoading={signup.isPending} type=\"submit\">\n {signup.isPending ? 'Creating account...' : 'Create account'}\n </Button>\n </form>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-subtle\">\n Already have an account?{' '}\n <ButtonLink asChild variant=\"interactive\" underline>\n <Link\n to=\"/auth/login\"\n search={invitationRedirect ? {redirect: invitationRedirect} : undefined}\n >\n Log in\n </Link>\n </ButtonLink>\n </Text>\n </AuthShell>\n );\n}\n"],"names":["signupBodySchema","AuthShell","rememberLastWorkspaceId","useRouteSearch","userWorkspacesQueryKey","displayNameFieldError","Button","ButtonLink","Callout","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","useQueryClient","Link","useNavigate","useAtom","useEffect","useRef","useState","EmailCodeVerification","useRefreshAuth","useSignupAuth","useResendEmailVerificationAuth","useVerifyEmailAuth","authFormDraftAtom","initialAuthFormDraft","validateRedirectSearch","signupErrorToFormError","authErrorMessage","extractInvitationToken","pendingInvitation","useInvitationContext","SignupPage","signup","verifyEmail","resendEmailVerification","refreshAuth","queryClient","navigate","search","invitationToken","redirect","invitationPreview","invitationPending","data","authFormDraft","setAuthFormDraft","emailChallenge","setEmailChallenge","nextResendAvailableAt","setNextResendAvailableAt","formError","setFormError","resendError","setResendError","invitationRefreshFailure","setInvitationRefreshFailure","isRetryingInvitationRefresh","setIsRetryingInvitationRefresh","draftRef","current","skipDraftPersistRef","refreshInvitationWorkspace","workspaceId","attempt","workspaces","getQueryData","memberships","some","id","form","defaultValues","email","password","name","onSubmit","value","undefined","parsed","parse","invitation_token","result","mutateAsync","membership","joinedWorkspace","workspaceName","userId","user","success","to","acceptError","error","message","token","Error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","retryInvitationAuthRefresh","state","values","setFieldValue","onResendVerificationEmail","isPending","challengeId","title","description","role","type","div","className","size","variant","isLoading","onClick","destination","isResending","isVerifying","onVerify","code","replace","onResend","onUseAnotherEmail","asChild","underline","headerTitle","headerDescription","isInvitationEmailLocked","Boolean","invitationRedirect","encodeURIComponent","persistDraft","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","autoComplete","onChange","handleChange","target","handleBlur","readOnly","iconRight","aria-hidden"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-onboarding-page.d.ts","sourceRoot":"","sources":["../../src/pages/workspace-onboarding-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workspace-onboarding-page.d.ts","sourceRoot":"","sources":["../../src/pages/workspace-onboarding-page.tsx"],"names":[],"mappings":"AAwCA,wBAAgB,uBAAuB,gCA6MtC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { slugifyName, slugSchema } from '@shipfox/api-common-dto';
|
|
2
3
|
import { createWorkspaceBodySchema } from '@shipfox/api-workspaces-dto';
|
|
3
|
-
import { displayNameFieldError } from '@shipfox/client-ui';
|
|
4
|
+
import { displayNameFieldError, SlugField } from '@shipfox/client-ui';
|
|
4
5
|
import { Button } from '@shipfox/react-ui/button';
|
|
5
6
|
import { Callout } from '@shipfox/react-ui/callout';
|
|
6
7
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@shipfox/react-ui/card';
|
|
@@ -12,7 +13,7 @@ import { useForm } from '@tanstack/react-form';
|
|
|
12
13
|
import { useNavigate } from '@tanstack/react-router';
|
|
13
14
|
import { useSetAtom } from 'jotai';
|
|
14
15
|
import { useState } from 'react';
|
|
15
|
-
import { useCreateWorkspaceAuth } from '#hooks/api/workspace-auth.js';
|
|
16
|
+
import { checkWorkspaceSlugAvailability, useCreateWorkspaceAuth } from '#hooks/api/workspace-auth.js';
|
|
16
17
|
import { useAuthState } from '#hooks/use-auth-state.js';
|
|
17
18
|
import { lastWorkspaceIdAtom, rememberLastWorkspaceId } from '#state/last-workspace.js';
|
|
18
19
|
import { workspaceOnboardingErrorToFormError } from './form-errors.js';
|
|
@@ -68,21 +69,27 @@ const previewBars = [
|
|
|
68
69
|
height: 74
|
|
69
70
|
}
|
|
70
71
|
];
|
|
72
|
+
function isSlugValid(value) {
|
|
73
|
+
return slugSchema.safeParse(value).success;
|
|
74
|
+
}
|
|
71
75
|
export function WorkspaceOnboardingPage() {
|
|
72
76
|
const createWorkspace = useCreateWorkspaceAuth();
|
|
73
77
|
const { user } = useAuthState();
|
|
74
78
|
const navigate = useNavigate();
|
|
75
79
|
const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);
|
|
76
80
|
const [formError, setFormError] = useState();
|
|
81
|
+
const [slugTouched, setSlugTouched] = useState(false);
|
|
77
82
|
const form = useForm({
|
|
78
83
|
defaultValues: {
|
|
79
|
-
name: ''
|
|
84
|
+
name: '',
|
|
85
|
+
slug: ''
|
|
80
86
|
},
|
|
81
87
|
onSubmit: async ({ value })=>{
|
|
82
88
|
setFormError(undefined);
|
|
83
89
|
try {
|
|
84
90
|
const command = createWorkspaceBodySchema.parse({
|
|
85
|
-
name: value.name
|
|
91
|
+
name: value.name,
|
|
92
|
+
slug: value.slug
|
|
86
93
|
});
|
|
87
94
|
const created = await createWorkspace.mutateAsync(command);
|
|
88
95
|
toast.success('Workspace created.');
|
|
@@ -95,14 +102,24 @@ export function WorkspaceOnboardingPage() {
|
|
|
95
102
|
// localStorage may throw in private browsing or quota-exceeded.
|
|
96
103
|
}
|
|
97
104
|
await navigate({
|
|
98
|
-
to: '/
|
|
105
|
+
to: '/w/$workspaceSlug',
|
|
99
106
|
params: {
|
|
100
|
-
|
|
107
|
+
workspaceSlug: created.slug
|
|
101
108
|
}
|
|
102
109
|
});
|
|
103
110
|
} catch (error) {
|
|
104
111
|
const mapped = workspaceOnboardingErrorToFormError(error);
|
|
105
|
-
|
|
112
|
+
if (mapped.kind === 'field') {
|
|
113
|
+
form.setFieldMeta(mapped.field, (previous)=>({
|
|
114
|
+
...previous,
|
|
115
|
+
errorMap: {
|
|
116
|
+
...previous.errorMap,
|
|
117
|
+
onServer: mapped.message
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
} else {
|
|
121
|
+
setFormError(mapped.message);
|
|
122
|
+
}
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
});
|
|
@@ -164,29 +181,66 @@ export function WorkspaceOnboardingPage() {
|
|
|
164
181
|
type: "error",
|
|
165
182
|
children: formError
|
|
166
183
|
}) : null,
|
|
167
|
-
/*#__PURE__*/
|
|
184
|
+
/*#__PURE__*/ _jsxs(CardContent, {
|
|
168
185
|
className: "flex flex-col gap-8",
|
|
169
|
-
children:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
children: [
|
|
187
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
188
|
+
name: "name",
|
|
189
|
+
validators: {
|
|
190
|
+
onBlur: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name),
|
|
191
|
+
onSubmit: ({ value })=>displayNameFieldError(value, 'Workspace name', createWorkspaceBodySchema.shape.name)
|
|
192
|
+
},
|
|
193
|
+
children: (field)=>/*#__PURE__*/ _jsx(FormField, {
|
|
194
|
+
label: "Workspace name",
|
|
195
|
+
id: "workspace-name",
|
|
196
|
+
error: fieldError(field),
|
|
197
|
+
children: /*#__PURE__*/ _jsx(FormFieldInput, {
|
|
198
|
+
autoComplete: "organization",
|
|
199
|
+
name: "name",
|
|
200
|
+
placeholder: "Acme",
|
|
201
|
+
type: "text",
|
|
202
|
+
value: field.state.value,
|
|
203
|
+
onChange: (event)=>{
|
|
204
|
+
const name = event.target.value;
|
|
205
|
+
field.handleChange(name);
|
|
206
|
+
if (!slugTouched) {
|
|
207
|
+
form.setFieldValue('slug', name ? slugifyName(name, {
|
|
208
|
+
fallback: 'workspace'
|
|
209
|
+
}) : '');
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
onBlur: field.handleBlur
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
}),
|
|
216
|
+
/*#__PURE__*/ _jsx(form.Field, {
|
|
217
|
+
name: "slug",
|
|
218
|
+
validators: {
|
|
219
|
+
onBlur: createWorkspaceBodySchema.shape.slug,
|
|
220
|
+
onSubmit: createWorkspaceBodySchema.shape.slug
|
|
221
|
+
},
|
|
222
|
+
children: (field)=>/*#__PURE__*/ _jsx(SlugField, {
|
|
223
|
+
id: "workspace-slug",
|
|
224
|
+
label: "Workspace slug",
|
|
225
|
+
name: "slug",
|
|
184
226
|
value: field.state.value,
|
|
185
|
-
onChange: (
|
|
186
|
-
|
|
227
|
+
onChange: (value)=>{
|
|
228
|
+
setSlugTouched(true);
|
|
229
|
+
field.handleChange(value);
|
|
230
|
+
},
|
|
231
|
+
onBlur: field.handleBlur,
|
|
232
|
+
error: fieldError(field),
|
|
233
|
+
description: /*#__PURE__*/ _jsx("span", {
|
|
234
|
+
className: "break-all font-code",
|
|
235
|
+
children: `${window.location.origin}/w/${field.state.value || 'acme'}`
|
|
236
|
+
}),
|
|
237
|
+
placeholder: "acme",
|
|
238
|
+
checkEnabled: slugTouched,
|
|
239
|
+
isValid: isSlugValid,
|
|
240
|
+
checkAvailability: checkWorkspaceSlugAvailability
|
|
187
241
|
})
|
|
188
|
-
|
|
189
|
-
|
|
242
|
+
})
|
|
243
|
+
]
|
|
190
244
|
}),
|
|
191
245
|
/*#__PURE__*/ _jsx(Button, {
|
|
192
246
|
className: "w-full",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pages/workspace-onboarding-page.tsx"],"sourcesContent":["import {createWorkspaceBodySchema} from '@shipfox/api-workspaces-dto';\nimport {displayNameFieldError} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@shipfox/react-ui/card';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useSetAtom} from 'jotai';\nimport {useState} from 'react';\nimport {useCreateWorkspaceAuth} from '#hooks/api/workspace-auth.js';\nimport {useAuthState} from '#hooks/use-auth-state.js';\nimport {lastWorkspaceIdAtom, rememberLastWorkspaceId} from '#state/last-workspace.js';\nimport {workspaceOnboardingErrorToFormError} from './form-errors.js';\n\nconst previewMetrics = [\n {label: 'Runs', value: '--'},\n {label: 'Passed', value: '--'},\n {label: 'Failed', value: '--'},\n {label: 'Duration', value: '--'},\n];\nconst previewBars = [\n {id: 'runs-start', height: 32},\n {id: 'runs-mid-low', height: 48},\n {id: 'runs-dip', height: 28},\n {id: 'runs-mid-high', height: 66},\n {id: 'runs-mid', height: 54},\n {id: 'runs-peak', height: 82},\n {id: 'runs-late-low', height: 44},\n {id: 'runs-late-high', height: 74},\n];\n\nexport function WorkspaceOnboardingPage() {\n const createWorkspace = useCreateWorkspaceAuth();\n const {user} = useAuthState();\n const navigate = useNavigate();\n const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);\n const [formError, setFormError] = useState<string | undefined>();\n\n const form = useForm({\n defaultValues: {name: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const command = createWorkspaceBodySchema.parse({name: value.name});\n const created = await createWorkspace.mutateAsync(command);\n toast.success('Workspace created.');\n // Pin the new workspace as the last-active one so a page refresh and\n // future visits to `/` land on it.\n try {\n setLastWorkspaceId(created.id);\n if (user?.id) rememberLastWorkspaceId(user.id, created.id);\n } catch {\n // localStorage may throw in private browsing or quota-exceeded.\n }\n await navigate({to: '/workspaces/$wid', params: {wid: created.id}});\n } catch (error) {\n const mapped = workspaceOnboardingErrorToFormError(error);\n setFormError(mapped.message);\n }\n },\n });\n\n return (\n <main className=\"min-h-screen bg-background-subtle-base px-24 py-32 max-[520px]:px-16\">\n <div className=\"mx-auto flex min-h-[calc(100vh-64px)] w-full max-w-[1120px] flex-col gap-24\">\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-10\">\n <div className=\"flex size-36 items-center justify-center rounded-8 border border-border-neutral-base bg-background-neutral-base shadow-button-neutral\">\n <Icon name=\"shipfox\" className=\"size-24 text-background-highlight-interactive\" />\n </div>\n <Text size=\"md\" bold>\n Shipfox\n </Text>\n </div>\n </header>\n\n <section className=\"grid flex-1 items-center gap-32 lg:grid-cols-[minmax(0,440px)_minmax(0,1fr)]\">\n <form\n className=\"relative z-10 w-full\"\n noValidate\n aria-labelledby=\"workspace-onboarding-title\"\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <Card className=\"gap-20 p-24 shadow-button-neutral\">\n <CardHeader className=\"gap-8\">\n <CardTitle id=\"workspace-onboarding-title\" variant=\"h1\">\n Create your workspace\n </CardTitle>\n <CardDescription>Give your team a place to collaborate.</CardDescription>\n </CardHeader>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <CardContent className=\"flex flex-col gap-8\">\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField label=\"Workspace name\" id=\"workspace-name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"organization\"\n name=\"name\"\n placeholder=\"Acme\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </CardContent>\n\n <Button\n className=\"w-full\"\n iconRight=\"chevronRight\"\n isLoading={createWorkspace.isPending}\n type=\"submit\"\n >\n {createWorkspace.isPending ? 'Creating workspace...' : 'Create workspace'}\n </Button>\n </Card>\n </form>\n\n <div className=\"hidden flex-col gap-18 lg:flex\" aria-hidden=\"true\">\n <div className=\"grid grid-cols-4 gap-12\">\n {previewMetrics.map((metric) => (\n <div\n className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-14 shadow-button-neutral\"\n key={metric.label}\n >\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {metric.label}\n </Text>\n <Text size=\"xl\" bold>\n {metric.value}\n </Text>\n </div>\n ))}\n </div>\n <div className=\"grid grid-cols-2 gap-18\">\n <PreviewPanel title=\"Performance over time\" />\n <PreviewPanel title=\"Duration distribution\" bars />\n </div>\n <div className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 shadow-button-neutral\">\n <Text size=\"sm\" bold>\n Jobs breakdown\n </Text>\n <div className=\"mt-14 flex flex-col gap-10\">\n {[0, 1, 2, 3].map((row) => (\n <div\n className=\"grid grid-cols-[1fr_80px_80px] gap-12 border-t border-border-neutral-base pt-10\"\n key={row}\n >\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n </section>\n </div>\n </main>\n );\n}\n\nfunction PreviewPanel({title, bars = false}: {title: string; bars?: boolean}) {\n return (\n <div className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 shadow-button-neutral\">\n <Text size=\"sm\" bold>\n {title}\n </Text>\n <div className=\"mt-16 flex h-[220px] items-end gap-8 border-b border-l border-border-neutral-base px-12 pb-10\">\n {previewBars.map((bar) => (\n <div\n className={\n bars\n ? 'w-full rounded-t-4 bg-background-neutral-disabled'\n : 'w-full rounded-full bg-background-neutral-disabled'\n }\n key={`${title}-${bar.id}`}\n style={{height: `${bar.height}%`}}\n />\n ))}\n </div>\n </div>\n );\n}\n"],"names":["createWorkspaceBodySchema","displayNameFieldError","Button","Callout","Card","CardContent","CardDescription","CardHeader","CardTitle","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","useNavigate","useSetAtom","useState","useCreateWorkspaceAuth","useAuthState","lastWorkspaceIdAtom","rememberLastWorkspaceId","workspaceOnboardingErrorToFormError","previewMetrics","label","value","previewBars","id","height","WorkspaceOnboardingPage","createWorkspace","user","navigate","setLastWorkspaceId","formError","setFormError","form","defaultValues","name","onSubmit","undefined","command","parse","created","mutateAsync","success","to","params","wid","error","mapped","message","main","className","div","header","size","bold","section","noValidate","aria-labelledby","event","preventDefault","stopPropagation","handleSubmit","variant","role","type","Field","validators","onBlur","shape","field","autoComplete","placeholder","state","onChange","handleChange","target","handleBlur","iconRight","isLoading","isPending","aria-hidden","map","metric","PreviewPanel","title","bars","row","bar","style"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/pages/workspace-onboarding-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createWorkspaceBodySchema} from '@shipfox/api-workspaces-dto';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@shipfox/react-ui/card';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useSetAtom} from 'jotai';\nimport {useState} from 'react';\nimport {checkWorkspaceSlugAvailability, useCreateWorkspaceAuth} from '#hooks/api/workspace-auth.js';\nimport {useAuthState} from '#hooks/use-auth-state.js';\nimport {lastWorkspaceIdAtom, rememberLastWorkspaceId} from '#state/last-workspace.js';\nimport {workspaceOnboardingErrorToFormError} from './form-errors.js';\n\nconst previewMetrics = [\n {label: 'Runs', value: '--'},\n {label: 'Passed', value: '--'},\n {label: 'Failed', value: '--'},\n {label: 'Duration', value: '--'},\n];\nconst previewBars = [\n {id: 'runs-start', height: 32},\n {id: 'runs-mid-low', height: 48},\n {id: 'runs-dip', height: 28},\n {id: 'runs-mid-high', height: 66},\n {id: 'runs-mid', height: 54},\n {id: 'runs-peak', height: 82},\n {id: 'runs-late-low', height: 44},\n {id: 'runs-late-high', height: 74},\n];\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function WorkspaceOnboardingPage() {\n const createWorkspace = useCreateWorkspaceAuth();\n const {user} = useAuthState();\n const navigate = useNavigate();\n const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);\n const [formError, setFormError] = useState<string | undefined>();\n const [slugTouched, setSlugTouched] = useState(false);\n\n const form = useForm({\n defaultValues: {name: '', slug: ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n const command = createWorkspaceBodySchema.parse({name: value.name, slug: value.slug});\n const created = await createWorkspace.mutateAsync(command);\n toast.success('Workspace created.');\n // Pin the new workspace as the last-active one so a page refresh and\n // future visits to `/` land on it.\n try {\n setLastWorkspaceId(created.id);\n if (user?.id) rememberLastWorkspaceId(user.id, created.id);\n } catch {\n // localStorage may throw in private browsing or quota-exceeded.\n }\n await navigate({to: '/w/$workspaceSlug', params: {workspaceSlug: created.slug}});\n } catch (error) {\n const mapped = workspaceOnboardingErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n return (\n <main className=\"min-h-screen bg-background-subtle-base px-24 py-32 max-[520px]:px-16\">\n <div className=\"mx-auto flex min-h-[calc(100vh-64px)] w-full max-w-[1120px] flex-col gap-24\">\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-10\">\n <div className=\"flex size-36 items-center justify-center rounded-8 border border-border-neutral-base bg-background-neutral-base shadow-button-neutral\">\n <Icon name=\"shipfox\" className=\"size-24 text-background-highlight-interactive\" />\n </div>\n <Text size=\"md\" bold>\n Shipfox\n </Text>\n </div>\n </header>\n\n <section className=\"grid flex-1 items-center gap-32 lg:grid-cols-[minmax(0,440px)_minmax(0,1fr)]\">\n <form\n className=\"relative z-10 w-full\"\n noValidate\n aria-labelledby=\"workspace-onboarding-title\"\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <Card className=\"gap-20 p-24 shadow-button-neutral\">\n <CardHeader className=\"gap-8\">\n <CardTitle id=\"workspace-onboarding-title\" variant=\"h1\">\n Create your workspace\n </CardTitle>\n <CardDescription>Give your team a place to collaborate.</CardDescription>\n </CardHeader>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <CardContent className=\"flex flex-col gap-8\">\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n onSubmit: ({value}) =>\n displayNameFieldError(\n value,\n 'Workspace name',\n createWorkspaceBodySchema.shape.name,\n ),\n }}\n >\n {(field) => (\n <FormField label=\"Workspace name\" id=\"workspace-name\" error={fieldError(field)}>\n <FormFieldInput\n autoComplete=\"organization\"\n name=\"name\"\n placeholder=\"Acme\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n const name = event.target.value;\n field.handleChange(name);\n if (!slugTouched) {\n form.setFieldValue(\n 'slug',\n name ? slugifyName(name, {fallback: 'workspace'}) : '',\n );\n }\n }}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createWorkspaceBodySchema.shape.slug,\n onSubmit: createWorkspaceBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"workspace-slug\"\n label=\"Workspace slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugTouched(true);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={\n <span className=\"break-all font-code\">\n {`${window.location.origin}/w/${field.state.value || 'acme'}`}\n </span>\n }\n placeholder=\"acme\"\n checkEnabled={slugTouched}\n isValid={isSlugValid}\n checkAvailability={checkWorkspaceSlugAvailability}\n />\n )}\n </form.Field>\n </CardContent>\n\n <Button\n className=\"w-full\"\n iconRight=\"chevronRight\"\n isLoading={createWorkspace.isPending}\n type=\"submit\"\n >\n {createWorkspace.isPending ? 'Creating workspace...' : 'Create workspace'}\n </Button>\n </Card>\n </form>\n\n <div className=\"hidden flex-col gap-18 lg:flex\" aria-hidden=\"true\">\n <div className=\"grid grid-cols-4 gap-12\">\n {previewMetrics.map((metric) => (\n <div\n className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-14 shadow-button-neutral\"\n key={metric.label}\n >\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {metric.label}\n </Text>\n <Text size=\"xl\" bold>\n {metric.value}\n </Text>\n </div>\n ))}\n </div>\n <div className=\"grid grid-cols-2 gap-18\">\n <PreviewPanel title=\"Performance over time\" />\n <PreviewPanel title=\"Duration distribution\" bars />\n </div>\n <div className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 shadow-button-neutral\">\n <Text size=\"sm\" bold>\n Jobs breakdown\n </Text>\n <div className=\"mt-14 flex flex-col gap-10\">\n {[0, 1, 2, 3].map((row) => (\n <div\n className=\"grid grid-cols-[1fr_80px_80px] gap-12 border-t border-border-neutral-base pt-10\"\n key={row}\n >\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n <div className=\"h-12 rounded-full bg-background-neutral-disabled\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n </section>\n </div>\n </main>\n );\n}\n\nfunction PreviewPanel({title, bars = false}: {title: string; bars?: boolean}) {\n return (\n <div className=\"rounded-8 border border-border-neutral-base bg-background-neutral-base p-16 shadow-button-neutral\">\n <Text size=\"sm\" bold>\n {title}\n </Text>\n <div className=\"mt-16 flex h-[220px] items-end gap-8 border-b border-l border-border-neutral-base px-12 pb-10\">\n {previewBars.map((bar) => (\n <div\n className={\n bars\n ? 'w-full rounded-t-4 bg-background-neutral-disabled'\n : 'w-full rounded-full bg-background-neutral-disabled'\n }\n key={`${title}-${bar.id}`}\n style={{height: `${bar.height}%`}}\n />\n ))}\n </div>\n </div>\n );\n}\n"],"names":["slugifyName","slugSchema","createWorkspaceBodySchema","displayNameFieldError","SlugField","Button","Callout","Card","CardContent","CardDescription","CardHeader","CardTitle","FormField","FormFieldInput","fieldError","Icon","toast","Text","useForm","useNavigate","useSetAtom","useState","checkWorkspaceSlugAvailability","useCreateWorkspaceAuth","useAuthState","lastWorkspaceIdAtom","rememberLastWorkspaceId","workspaceOnboardingErrorToFormError","previewMetrics","label","value","previewBars","id","height","isSlugValid","safeParse","success","WorkspaceOnboardingPage","createWorkspace","user","navigate","setLastWorkspaceId","formError","setFormError","slugTouched","setSlugTouched","form","defaultValues","name","slug","onSubmit","undefined","command","parse","created","mutateAsync","to","params","workspaceSlug","error","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","main","className","div","header","size","bold","section","noValidate","aria-labelledby","event","preventDefault","stopPropagation","handleSubmit","variant","role","type","Field","validators","onBlur","shape","autoComplete","placeholder","state","onChange","target","handleChange","setFieldValue","fallback","handleBlur","description","span","window","location","origin","checkEnabled","isValid","checkAvailability","iconRight","isLoading","isPending","aria-hidden","map","metric","PreviewPanel","title","bars","row","bar","style"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;;uCAI9B;QAAC,OAAO,EAAE,aAAa,CAAA;KAAC;;;AADlD,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;;uCAI9B;QAAC,OAAO,EAAE,aAAa,CAAA;KAAC;;;AADlD,wBAiBG"}
|
package/dist/routes/index.js
CHANGED
|
@@ -18,9 +18,9 @@ export default defineRoute({
|
|
|
18
18
|
...rest
|
|
19
19
|
].find((workspace)=>workspace.id === getLastWorkspaceId(principalId)) : undefined;
|
|
20
20
|
throw redirect({
|
|
21
|
-
to: '/
|
|
21
|
+
to: '/w/$workspaceSlug',
|
|
22
22
|
params: {
|
|
23
|
-
|
|
23
|
+
workspaceSlug: (target ?? first).slug
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
},
|
package/dist/routes/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/index.tsx"],"sourcesContent":["import type {RouterContext} from '@shipfox/client-shell/runtime';\nimport {defineRoute, getLastWorkspaceId} from '@shipfox/client-shell/runtime';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {redirect} from '@tanstack/react-router';\n\nexport default defineRoute({\n beforeLoad: ({context}: {context: RouterContext}) => {\n const auth = context.auth;\n if (!auth || auth.isLoading) return;\n if (!auth.isAuthenticated) throw redirect({to: '/auth/login'});\n const [first, ...rest] = auth.workspaces;\n if (!first) throw redirect({to: '/setup/workspaces/new'});\n const principalId = auth.user?.id;\n const target = principalId\n ? [first, ...rest].find((workspace) => workspace.id === getLastWorkspaceId(principalId))\n : undefined;\n throw redirect({to: '/
|
|
1
|
+
{"version":3,"sources":["../../src/routes/index.tsx"],"sourcesContent":["import type {RouterContext} from '@shipfox/client-shell/runtime';\nimport {defineRoute, getLastWorkspaceId} from '@shipfox/client-shell/runtime';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {redirect} from '@tanstack/react-router';\n\nexport default defineRoute({\n beforeLoad: ({context}: {context: RouterContext}) => {\n const auth = context.auth;\n if (!auth || auth.isLoading) return;\n if (!auth.isAuthenticated) throw redirect({to: '/auth/login'});\n const [first, ...rest] = auth.workspaces;\n if (!first) throw redirect({to: '/setup/workspaces/new'});\n const principalId = auth.user?.id;\n const target = principalId\n ? [first, ...rest].find((workspace) => workspace.id === getLastWorkspaceId(principalId))\n : undefined;\n throw redirect({\n to: '/w/$workspaceSlug',\n params: {workspaceSlug: (target ?? first).slug},\n });\n },\n component: FullPageLoader,\n});\n"],"names":["defineRoute","getLastWorkspaceId","FullPageLoader","redirect","beforeLoad","context","auth","isLoading","isAuthenticated","to","first","rest","workspaces","principalId","user","id","target","find","workspace","undefined","params","workspaceSlug","slug","component"],"mappings":"AACA,SAAQA,WAAW,EAAEC,kBAAkB,QAAO,gCAAgC;AAC9E,SAAQC,cAAc,QAAO,2BAA2B;AACxD,SAAQC,QAAQ,QAAO,yBAAyB;AAEhD,eAAeH,YAAY;IACzBI,YAAY,CAAC,EAACC,OAAO,EAA2B;QAC9C,MAAMC,OAAOD,QAAQC,IAAI;QACzB,IAAI,CAACA,QAAQA,KAAKC,SAAS,EAAE;QAC7B,IAAI,CAACD,KAAKE,eAAe,EAAE,MAAML,SAAS;YAACM,IAAI;QAAa;QAC5D,MAAM,CAACC,OAAO,GAAGC,KAAK,GAAGL,KAAKM,UAAU;QACxC,IAAI,CAACF,OAAO,MAAMP,SAAS;YAACM,IAAI;QAAuB;QACvD,MAAMI,cAAcP,KAAKQ,IAAI,EAAEC;QAC/B,MAAMC,SAASH,cACX;YAACH;eAAUC;SAAK,CAACM,IAAI,CAAC,CAACC,YAAcA,UAAUH,EAAE,KAAKd,mBAAmBY,gBACzEM;QACJ,MAAMhB,SAAS;YACbM,IAAI;YACJW,QAAQ;gBAACC,eAAe,AAACL,CAAAA,UAAUN,KAAI,EAAGY,IAAI;YAAA;QAChD;IACF;IACAC,WAAWrB;AACb,GAAG"}
|