Skip to content

Commit 6401765

Browse files
committed
Merge branches 'master' and 'master' of https://github.com/codesandbox/codesandbox-client into draft/priceless-diffie
2 parents 26be776 + 01388fd commit 6401765

File tree

5 files changed

+215
-8
lines changed

5 files changed

+215
-8
lines changed

packages/app/src/app/components/WorkspaceSetup/steps/Create.tsx

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect, useRef, useState } from 'react';
2-
import { useActions, useAppState } from 'app/overmind';
2+
import { useActions, useAppState, useEffects } from 'app/overmind';
3+
import { useHistory } from 'react-router-dom';
34
import {
45
Stack,
56
Button,
@@ -34,6 +35,8 @@ export const Create: React.FC<StepProps> = ({
3435
const [loading, setLoading] = useState(false);
3536
const [error, setError] = useState<string>('');
3637
const inputRef = useRef<HTMLInputElement>(null);
38+
const [disableButton, setDisableButton] = useState(false);
39+
const [loadingButton, setLoadingButton] = useState(false);
3740

3841
const urlWorkspaceId = getQueryParam('workspace');
3942
const teamIsAlreadyCreated = !!urlWorkspaceId;
@@ -143,6 +146,7 @@ export const Create: React.FC<StepProps> = ({
143146
defaultValue={teamIsAlreadyCreated ? activeTeamInfo.name : ''}
144147
onChange={handleInput}
145148
ref={inputRef}
149+
disabled={disableButton || loading}
146150
/>
147151

148152
{error && (
@@ -157,26 +161,145 @@ export const Create: React.FC<StepProps> = ({
157161
</Stack>
158162

159163
<Button
160-
loading={loading}
161-
disabled={loading || !!error}
164+
loading={loadingButton || loading}
165+
disabled={disableButton || loading || !!error}
162166
type="submit"
163167
size="large"
164168
autoWidth
165169
>
166170
Next
167171
</Button>
168172
</Stack>
173+
174+
<JoinWorkspace
175+
onStart={() => setLoadingButton(true)}
176+
onDidFinish={() => setLoadingButton(false)}
177+
onDidFindWorkspace={() => setDisableButton(true)}
178+
onRejectWorkspace={() => setDisableButton(false)}
179+
/>
180+
169181
<Link
170182
href={docsUrl('/learn/plans/workspace')}
171183
target="_blank"
172184
rel="noreferrer"
173185
>
174-
<Stack gap={1} align="center">
186+
<Stack gap={2} align="center">
175187
<Text>More about teams and workspaces</Text>
176-
<Icon name="external" size={16} />
188+
<Icon name="external" size={14} />
177189
</Stack>
178190
</Link>
179191
</Stack>
180192
</AnimatedStep>
181193
);
182194
};
195+
196+
const JoinWorkspace: React.FC<{
197+
onDidFindWorkspace: () => void;
198+
onRejectWorkspace: () => void;
199+
onStart: () => void;
200+
onDidFinish: () => void;
201+
}> = ({ onDidFindWorkspace, onStart, onDidFinish, onRejectWorkspace }) => {
202+
const [hidden, setHidden] = useState(true);
203+
const effects = useEffects();
204+
const actions = useActions();
205+
const [eligibleWorkspace, setEligibleWorkspace] = useState<{
206+
id: string;
207+
name: string;
208+
}>(null);
209+
const history = useHistory();
210+
const [loading, setLoading] = useState(false);
211+
212+
useEffect(() => {
213+
onStart();
214+
215+
effects.gql.queries
216+
.getEligibleWorkspaces({})
217+
.then(result => {
218+
const hasEligibleWorkspace = result.me.eligibleWorkspaces.length > 0;
219+
if (hasEligibleWorkspace) {
220+
onDidFindWorkspace();
221+
setEligibleWorkspace(result.me.eligibleWorkspaces[0]);
222+
}
223+
})
224+
.catch(e => {})
225+
.finally(() => {
226+
onDidFinish();
227+
});
228+
}, []);
229+
230+
const joinWorkspace = () => {
231+
setLoading(true);
232+
effects.gql.mutations
233+
.joinEligibleWorkspace({
234+
workspaceId: eligibleWorkspace.id,
235+
})
236+
.then(async () => {
237+
await actions.setActiveTeam({ id: eligibleWorkspace.id });
238+
await actions.dashboard.getTeams();
239+
240+
history.push(`/dashboard/recent?workspace=${eligibleWorkspace.id}`);
241+
});
242+
};
243+
244+
if (eligibleWorkspace && hidden) {
245+
return (
246+
<>
247+
<Text css={{ textAlign: 'center' }}>or</Text>
248+
<Stack
249+
direction="vertical"
250+
css={{
251+
background: '#1A1A1A',
252+
padding: 18,
253+
marginLeft: -18,
254+
marginRight: -18,
255+
borderRadius: 4,
256+
}}
257+
>
258+
<Text
259+
margin={0}
260+
as="h1"
261+
color="#fff"
262+
weight="medium"
263+
fontFamily="everett"
264+
size={24}
265+
>
266+
You have been invited to join the {eligibleWorkspace.name} workspace
267+
</Text>
268+
<p>
269+
Your email matches the domain of the {eligibleWorkspace.name}{' '}
270+
workspace.
271+
</p>
272+
273+
<Stack gap={4} css={{ marginTop: 32 }}>
274+
<Button
275+
autoWidth
276+
loading={loading}
277+
type="submit"
278+
size="large"
279+
onClick={joinWorkspace}
280+
css={{ flex: 1 }}
281+
>
282+
Join workspace
283+
</Button>
284+
285+
<Button
286+
autoWidth
287+
type="submit"
288+
size="large"
289+
css={{ flex: 1 }}
290+
variant="secondary"
291+
onClick={() => {
292+
setHidden(false);
293+
onRejectWorkspace();
294+
}}
295+
>
296+
Reject
297+
</Button>
298+
</Stack>
299+
</Stack>
300+
</>
301+
);
302+
}
303+
304+
return null;
305+
};

packages/app/src/app/components/dashboard/InputText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const StyledInput = styled.input<{ isInvalid?: boolean }>`
1616

1717
${props => (props.isInvalid ? 'outline: 1px solid #EB5E5E;' : '')}
1818

19-
&:hover {
19+
&:hover:not(:disabled) {
2020
box-shadow: 0 0 0 2px #e5e5e51a;
2121
}
2222

packages/app/src/app/graphql/types.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,24 @@ export type RootQueryType = {
155155
* ```
156156
*/
157157
recentTeamsByRepository: Array<TeamPreview>;
158-
/** Get a sandbox */
158+
/**
159+
* Get a sandbox by its (short) ID
160+
*
161+
* Requires the current user have read authorization (or that the sandbox is public). Otherwise
162+
* returns an error (`"unauthorized"`).
163+
*/
159164
sandbox: Maybe<Sandbox>;
165+
/**
166+
* Returns a sandbox's team ID if the current user is eligible to join that workspace
167+
*
168+
* This query is designed for use in 404 experience where the current user does not have access
169+
* to the resource but *might* have access if they accept an open invitation to its workspace.
170+
* Returns null if no such open invitation exists, or an error if no user is authenticated.
171+
*
172+
* For a list of all workspaces the user is eligible to join, see `query eligibleWorkspaces`.
173+
* The ID returned by this query can be used in `mutation joinEligibleWorkspace`.
174+
*/
175+
sandboxEligibleWorkspace: Maybe<TeamPreview>;
160176
/** A team from an invite token */
161177
teamByToken: Maybe<TeamPreview>;
162178
};
@@ -227,6 +243,10 @@ export type RootQueryTypeSandboxArgs = {
227243
sandboxId: Scalars['ID'];
228244
};
229245

246+
export type RootQueryTypeSandboxEligibleWorkspaceArgs = {
247+
sandboxId: Scalars['ID'];
248+
};
249+
230250
export type RootQueryTypeTeamByTokenArgs = {
231251
inviteToken: Scalars['String'];
232252
};
@@ -496,7 +516,11 @@ export type TeamLimits = {
496516
includedDrafts: Scalars['Int'];
497517
/** Number of workspace members included with the team's subscription, including add-ons */
498518
includedMembers: Scalars['Int'];
499-
/** Number of sandboxes included with the team's subscription, including add-ons */
519+
/** Number of included private browser sandboxes with the team's subscription */
520+
includedPrivateSandboxes: Scalars['Int'];
521+
/** Number of included public browser sandboxes with the team's subscription */
522+
includedPublicSandboxes: Scalars['Int'];
523+
/** DEPRECATED: Number of sandboxes included with the team's subscription */
500524
includedSandboxes: Scalars['Int'];
501525
/** Storage (in GB) included with the team's subscription, including add-ons */
502526
includedStorage: Scalars['Int'];
@@ -2173,12 +2197,14 @@ export type RootMutationTypeAddSandboxesToAlbumArgs = {
21732197

21742198
export type RootMutationTypeAddToCollectionArgs = {
21752199
collectionPath: Scalars['String'];
2200+
privacy: InputMaybe<Scalars['Int']>;
21762201
sandboxIds: InputMaybe<Array<InputMaybe<Scalars['ID']>>>;
21772202
teamId: InputMaybe<Scalars['UUID4']>;
21782203
};
21792204

21802205
export type RootMutationTypeAddToCollectionOrTeamArgs = {
21812206
collectionPath: InputMaybe<Scalars['String']>;
2207+
privacy: InputMaybe<Scalars['Int']>;
21822208
sandboxIds: InputMaybe<Array<InputMaybe<Scalars['ID']>>>;
21832209
teamId: InputMaybe<Scalars['UUID4']>;
21842210
};
@@ -5683,6 +5709,15 @@ export type SetTeamMetadataMutation = {
56835709
};
56845710
};
56855711

5712+
export type JoinEligibleWorkspaceMutationVariables = Exact<{
5713+
workspaceId: Scalars['ID'];
5714+
}>;
5715+
5716+
export type JoinEligibleWorkspaceMutation = {
5717+
__typename?: 'RootMutationType';
5718+
joinEligibleWorkspace: { __typename?: 'Team'; id: any };
5719+
};
5720+
56865721
export type RecentlyDeletedTeamSandboxesQueryVariables = Exact<{
56875722
teamId: Scalars['UUID4'];
56885723
}>;
@@ -6630,6 +6665,24 @@ export type GetSandboxWithTemplateQuery = {
66306665
} | null;
66316666
};
66326667

6668+
export type GetEligibleWorkspacesQueryVariables = Exact<{
6669+
[key: string]: never;
6670+
}>;
6671+
6672+
export type GetEligibleWorkspacesQuery = {
6673+
__typename?: 'RootQueryType';
6674+
me: {
6675+
__typename?: 'CurrentUser';
6676+
eligibleWorkspaces: Array<{
6677+
__typename?: 'TeamPreview';
6678+
id: any;
6679+
avatarUrl: string | null;
6680+
name: string;
6681+
shortid: string;
6682+
}>;
6683+
} | null;
6684+
};
6685+
66336686
export type RecentNotificationFragment = {
66346687
__typename?: 'Notification';
66356688
id: any;

packages/app/src/app/overmind/effects/gql/dashboard/mutations.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ import {
6363
UpdateUsageSubscriptionMutation,
6464
SetTeamMetadataMutation,
6565
SetTeamMetadataMutationVariables,
66+
JoinEligibleWorkspaceMutation,
67+
JoinEligibleWorkspaceMutationVariables,
6668
} from 'app/graphql/types';
6769
import { gql, Query } from 'overmind-graphql';
6870

@@ -490,3 +492,14 @@ export const setTeamMetadata: Query<
490492
}
491493
${teamFragmentDashboard}
492494
`;
495+
496+
export const joinEligibleWorkspace: Query<
497+
JoinEligibleWorkspaceMutation,
498+
JoinEligibleWorkspaceMutationVariables
499+
> = gql`
500+
mutation JoinEligibleWorkspace($workspaceId: ID!) {
501+
joinEligibleWorkspace(workspaceId: $workspaceId) {
502+
id
503+
}
504+
}
505+
`;

packages/app/src/app/overmind/effects/gql/dashboard/queries.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import {
4141
GetFullGitHubOrganizationReposQueryVariables,
4242
GetSandboxWithTemplateQuery,
4343
GetSandboxWithTemplateQueryVariables,
44+
GetEligibleWorkspacesQuery,
45+
GetEligibleWorkspacesQueryVariables,
4446
} from 'app/graphql/types';
4547
import { gql, Query } from 'overmind-graphql';
4648

@@ -399,3 +401,19 @@ export const getSandboxWithTemplate: Query<
399401
}
400402
}
401403
`;
404+
405+
export const getEligibleWorkspaces: Query<
406+
GetEligibleWorkspacesQuery,
407+
GetEligibleWorkspacesQueryVariables
408+
> = gql`
409+
query GetEligibleWorkspaces {
410+
me {
411+
eligibleWorkspaces {
412+
id
413+
avatarUrl
414+
name
415+
shortid
416+
}
417+
}
418+
}
419+
`;

0 commit comments

Comments
 (0)