feat(auth): collect the workspace logo on the sign-up creation step (#21723)
## What & why A single, consistent **workspace-creation step** for both multi-workspace and single-workspace self-host — collecting **name + logo** (and the **subdomain** in multi-workspace) — which **removes the duplicate name/logo prompt** that previously reappeared on the workspace subdomain (reported after #21641). ## Changes **One creation form for both modes** - With 0 workspaces, both multi-workspace and single-workspace route to the shared `SignInUpWorkspaceCreationForm`; `SignInUp` renders it for the `WorkspaceCreation` step regardless of domain/scope. - The subdomain field shows only in multi-workspace; single-workspace keeps its fixed address. **Logo on the creation step** - New scoped `uploadNewWorkspaceLogo(workspaceId, file)` mutation: the creator sets a logo on their just-created `PENDING_CREATION` workspace via the workspace-agnostic token (membership enforced — only the creator is a member at that point), reusing `uploadWorkspacePicture`. Upload size is capped via `settings.storage.maxFileSize` (also applied to the existing logo / profile-picture uploads). - The picked file is held locally (object-URL preview, revoked on unmount) and uploaded right after creation (non-fatal on failure). **Onboarding step → pure activation loader** - The old "Create your workspace" form (name + logo) is removed. The onboarding step now activates the pending workspace on mount and shows the loader, with a **Retry** action on failure. ## Testing - typecheck (front + server) ✅; oxlint + oxfmt clean on changed files ✅ - Unit tests: `auth.resolver.spec`, `useWorkspaceSubdomainField`, `SignInUpWorkspaceCreationForm` (multi + single-workspace), `useAuth` ✅ - Metadata GraphQL + `twenty-client-sdk` schema regenerated. Follow-up to #21641. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@ const testCases = [
|
||||
{ loc: AppPath.SignInUp, res: true },
|
||||
{ loc: AppPath.Invite, res: true },
|
||||
{ loc: AppPath.ResetPassword, res: true },
|
||||
{ loc: AppPath.CreateWorkspace, res: true },
|
||||
{ loc: AppPath.WorkspaceActivation, res: true },
|
||||
{ loc: AppPath.SyncEmails, res: true },
|
||||
{ loc: AppPath.InviteTeam, res: true },
|
||||
{ loc: AppPath.PlanRequired, res: true },
|
||||
|
||||
@@ -17,7 +17,7 @@ export const useShowAuthModal = () => {
|
||||
isMatchingLocation(location, AppPath.VerifyEmail) ||
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.SignInUp) ||
|
||||
isMatchingLocation(location, AppPath.CreateWorkspace) ||
|
||||
isMatchingLocation(location, AppPath.WorkspaceActivation) ||
|
||||
isMatchingLocation(location, AppPath.PlanRequired) ||
|
||||
isMatchingLocation(location, AppPath.PlanRequiredSuccess) ||
|
||||
isMatchingLocation(location, AppPath.BookCallDecision) ||
|
||||
|
||||
+10
-28
@@ -6,8 +6,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { countAvailableWorkspaces } from '@/auth/utils/availableWorkspacesUtils';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
@@ -21,7 +21,6 @@ import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/s
|
||||
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
@@ -44,11 +43,7 @@ import {
|
||||
MenuItemSelectAvatar,
|
||||
UndecoratedLink,
|
||||
} from 'twenty-ui/navigation';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import {
|
||||
type AvailableWorkspace,
|
||||
SignUpInNewWorkspaceDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { type AvailableWorkspace } from '~/generated-metadata/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
@@ -64,19 +59,15 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
const availableWorkspacesCount =
|
||||
countAvailableWorkspaces(availableWorkspaces);
|
||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const { signOut } = useAuth();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { colorScheme, colorSchemeList } = useColorScheme();
|
||||
const supportChat = useAtomStateValue(supportChatState);
|
||||
const isSupportChatConfigured =
|
||||
supportChat?.supportDriver === 'FRONT' &&
|
||||
isNonEmptyString(supportChat.supportFrontChatId);
|
||||
|
||||
const [signUpInNewWorkspaceMutation] = useMutation(
|
||||
SignUpInNewWorkspaceDocument,
|
||||
);
|
||||
|
||||
const setMultiWorkspaceDropdown = useSetAtomState(
|
||||
multiWorkspaceDropdownState,
|
||||
);
|
||||
@@ -94,23 +85,14 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// The workspace name (and logo + subdomain) are collected by the shared
|
||||
// creation form on the root domain, so we send the user there on the
|
||||
// WorkspaceCreation step instead of creating a nameless workspace on the fly.
|
||||
const createWorkspace = () => {
|
||||
signUpInNewWorkspaceMutation({
|
||||
onCompleted: async (data) => {
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(data.signUpInNewWorkspace.workspace.workspaceUrls),
|
||||
AppPath.Verify,
|
||||
{
|
||||
loginToken: data.signUpInNewWorkspace.loginToken.token,
|
||||
},
|
||||
'_blank',
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
enqueueErrorSnackBar({
|
||||
...(CombinedGraphQLErrors.is(error) ? { apolloError: error } : {}),
|
||||
});
|
||||
},
|
||||
closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
|
||||
redirectToDefaultDomain({
|
||||
pathname: AppPath.SignInUp,
|
||||
searchParams: { action: 'create-new-workspace' },
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user