Skip to content

[pull] master from codesandbox:master #1014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 11, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -115,7 +115,7 @@
"comlink-loader": "^2.0.0",
"compare-versions": "^3.1.0",
"console": "^0.7.2",
"csb-console-feed": "^3.7.0",
"console-feed": "^3.1.9",
"cropperjs": "^1.5.11",
"css-modules-loader-core": "^1.1.0",
"date-fns": "^2.4.1",
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Select from '@codesandbox/common/lib/components/Select';
import theme from '@codesandbox/common/lib/theme';
import { listen, dispatch } from 'codesandbox-api';
import { Decode, Console as ConsoleFeed } from 'csb-console-feed';
import { Decode, Console as ConsoleFeed } from 'console-feed';
import { debounce } from 'lodash-es';
import React from 'react';
import ClearIcon from 'react-icons/lib/md/block';
36 changes: 16 additions & 20 deletions packages/app/src/app/components/dashboard/InputText.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { InputHTMLAttributes } from 'react';
import React, { InputHTMLAttributes, forwardRef } from 'react';
import styled from 'styled-components';
import { Stack } from '@codesandbox/components';
import { Label } from './Label';
@@ -32,23 +32,19 @@ interface InputTextProps extends InputHTMLAttributes<HTMLInputElement> {
hideLabel?: boolean;
}

export const InputText = ({
id,
label,
name,
isInvalid,
hideLabel,
...restProps
}: InputTextProps) => (
<Stack gap={2} direction="vertical">
{!hideLabel && <Label htmlFor={id}>{label}</Label>}
<StyledInput
id={id}
name={name}
type="text"
aria-label={label}
isInvalid={isInvalid}
{...restProps}
/>
</Stack>
export const InputText = forwardRef<HTMLInputElement, InputTextProps>(
({ id, label, name, isInvalid, hideLabel, ...restProps }, ref) => (
<Stack gap={2} direction="vertical">
{!hideLabel && <Label htmlFor={id}>{label}</Label>}
<StyledInput
id={id}
name={name}
type="text"
aria-label={label}
isInvalid={isInvalid}
ref={ref}
{...restProps}
/>
</Stack>
)
);
4 changes: 2 additions & 2 deletions packages/app/src/app/components/dashboard/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ const StyledTextarea = styled.textarea<{ resize: boolean }>`

interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
id: string;
label: string;
label?: string;
name: string;
resize?: boolean;
}
@@ -39,7 +39,7 @@ export const Textarea = ({
}: TextareaProps) => {
return (
<Stack gap={2} direction="vertical">
<Label htmlFor={id}>{label}</Label>
{label && <Label htmlFor={id}>{label}</Label>}
<StyledTextarea id={id} name={name} resize={resize} {...restProps} />
</Stack>
);
2 changes: 1 addition & 1 deletion packages/app/src/app/overmind/actions.ts
Original file line number Diff line number Diff line change
@@ -619,7 +619,7 @@ export const openCreateTeamModal = (
props?: OpenCreateTeamModalParams
) => {
actions.modals.newTeamModal.open({
step: props?.step ?? 'create',
step: props?.step ?? 'name',
hasNextStep: props?.hasNextStep ?? true,
});
};
Original file line number Diff line number Diff line change
@@ -92,7 +92,7 @@ export async function initializeBrowserFS({
fs: 'JSDelivrRequest',
options: {
dependency: 'typescript',
version: '4.9.3',
version: '5.2.2',
},
};
}
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ export const TeamImport = ({ onComplete }: { onComplete: () => void }) => {
const { restrictsPublicRepos } = useGitHubPermissions();

return (
<Element css={{ width: '100%', height: '546px' }} padding={8}>
<Element css={{ width: '100%' }} padding={12}>
<Stack
align="center"
direction="vertical"
@@ -47,20 +47,22 @@ export const TeamImport = ({ onComplete }: { onComplete: () => void }) => {
</Element>
)}
</Stack>
<Button
css={{ width: 'auto' }}
onClick={() => {
track('New Team - Done import', {
codesandbox: 'V1',
event_source: 'UI',
});
<Stack css={{ width: '100%', justifyContent: 'flex-end' }}>
<Button
css={{ width: 'auto' }}
onClick={() => {
track('New Team - Done import', {
codesandbox: 'V1',
event_source: 'UI',
});

onComplete();
}}
variant="link"
>
Done
</Button>
onComplete();
}}
variant="link"
>
Done
</Button>
</Stack>
</Stack>
</Element>
);
Original file line number Diff line number Diff line change
@@ -154,21 +154,24 @@ export const TeamMembers: React.FC<{
width: '100%',
}}
>
<Text
as="h2"
size={32}
weight="500"
align="center"
css={{
margin: 0,
color: '#ffffff',
fontFamily: 'Everett, sans-serif',
lineHeight: '42px',
letterSpacing: '-0.01em',
}}
>
{activeTeamInfo.name}
</Text>
<Stack gap={2} direction="vertical">
<Text
as="h2"
size={32}
weight="500"
align="center"
css={{
margin: 0,
color: '#ffffff',
fontFamily: 'Everett, sans-serif',
lineHeight: '42px',
letterSpacing: '-0.01em',
}}
>
Invite team members
</Text>
<Text color="#999">Insert email addresses separated by a comma</Text>
</Stack>
<Stack
as="form"
onSubmit={handleSubmit}
@@ -178,9 +181,7 @@ export const TeamMembers: React.FC<{
>
<Stack gap={4} direction="vertical">
<Textarea
aria-describedby="invitees-role"
id="member"
label="Invite additional team members (Insert email addresses separated by a comma)"
name="members"
value={addressesString}
onChange={e => {
@@ -208,7 +209,7 @@ export const TeamMembers: React.FC<{
) : null}

<StyledButton loading={inviteLoading} type="submit">
Invite members
Send invites
</StyledButton>
</Stack>
<Text
@@ -238,7 +239,7 @@ export const TeamMembers: React.FC<{
<Stack
css={{
width: '100%',
padding: '48px 56px',
padding: '48px',
}}
justify={hideSkip ? 'center' : 'space-between'}
>
@@ -266,7 +267,7 @@ export const TeamMembers: React.FC<{
}}
variant="link"
>
Skip
Next
</Button>
)}
</Stack>
Original file line number Diff line number Diff line change
@@ -1,56 +1,55 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useActions, useAppState } from 'app/overmind';
import { Stack, Text, Link, Element, Icon } from '@codesandbox/components';
import { InputText } from 'app/components/dashboard/InputText';
import { StyledButton } from 'app/components/dashboard/Button';
import track from '@codesandbox/common/lib/utils/analytics';
import { dashboard as dashboardURLs } from '@codesandbox/common/lib/utils/url-generator';
import { useCreateCheckout } from 'app/hooks';

export const TeamCreate: React.FC<{ onComplete: () => void }> = () => {
const { dashboard } = useAppState();
export const TeamName: React.FC<{ onComplete: () => void }> = ({
onComplete,
}) => {
const { dashboard, activeTeamInfo } = useAppState();
const actions = useActions();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [existingTeamError, setExistingTeamError] = useState(false);
const [checkout, createCheckout] = useCreateCheckout();
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (checkout.status === 'error') {
setError(`Could not create stripe checkout link. ${checkout.error}`);
if (!activeTeamInfo || !inputRef.current) {
return;
}
}, [checkout]);

inputRef.current.focus();
inputRef.current.select();
}, [activeTeamInfo]);

const onSubmit = async event => {
event.preventDefault();
const teamName = event.target.name.value?.trim();

if (teamName) {
track('New Team - Create Team', {
if (teamName && teamName !== activeTeamInfo?.name) {
track('New Team - Set Name', {
codesandbox: 'V1',
event_source: 'UI',
});

setError(null);
setLoading(true);
try {
const newTeam = await actions.dashboard.createTeam({
teamName,
});

await createCheckout({
team_id: newTeam.id,
success_path: dashboardURLs.recent(newTeam.id, {
new_workspace: 'true',
}),
utm_source: 'pro_page',
await actions.dashboard.setTeamInfo({
name: teamName,
description: null,
file: null,
});
} catch {
setError('There was a problem creating your team');
setError('There was a problem updating your workspace');
} finally {
setLoading(false);
}
}

onComplete();
};

const handleInput = e => {
@@ -68,7 +67,11 @@ export const TeamCreate: React.FC<{ onComplete: () => void }> = () => {

// Check if there's any team with the same name.
setExistingTeamError(
Boolean(dashboard.teams.find(team => team.name === trimmedName))
Boolean(
dashboard.teams.find(
team => team.name === trimmedName && team.id !== activeTeamInfo?.id
)
)
);
};

@@ -97,8 +100,9 @@ export const TeamCreate: React.FC<{ onComplete: () => void }> = () => {
letterSpacing: '-0.01em',
}}
>
Name your workspace
Welcome to Pro
</Text>
<Text color="#999">Let&apos;s give your workspace a name</Text>
</Stack>
<Stack
as="form"
@@ -115,7 +119,9 @@ export const TeamCreate: React.FC<{ onComplete: () => void }> = () => {
required
autoFocus
hideLabel
defaultValue={activeTeamInfo?.name}
onChange={handleInput}
ref={inputRef}
/>

{existingTeamError && (
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import css from '@styled-system/css';
import {
IconButton,
Stack,
@@ -11,14 +10,14 @@ import Modal from 'app/components/Modal';
import { useActions, useAppState } from 'app/overmind';

import track from '@codesandbox/common/lib/utils/analytics';
import { TeamCreate } from './TeamCreate';
import { TeamName } from './TeamName';
import { TeamMembers } from './TeamMembers';
import { TeamImport } from './TeamImport';

export type TeamStep = 'create' | 'members' | 'import';
export type TeamStep = 'name' | 'members' | 'import';

const NEXT_STEP: Record<TeamStep, TeamStep | null> = {
create: null,
name: 'members',
members: 'import',
import: null,
};
@@ -31,7 +30,7 @@ type NewTeamProps = {
const NewTeam: React.FC<NewTeamProps> = ({ step, hasNextStep, onClose }) => {
const { activeTeamInfo } = useAppState();
const [currentStep, setCurrentStep] = React.useState<TeamStep>(
step ?? 'create'
step ?? 'name'
);

const nextStep =
@@ -51,16 +50,12 @@ const NewTeam: React.FC<NewTeamProps> = ({ step, hasNextStep, onClose }) => {
<>
<Element padding={6}>
<Stack align="center" justify="space-between">
<Text
css={css({
color: '#808080',
})}
size={3}
>
{activeTeamInfo && currentStep !== 'create'
<Text color="#999" size={3}>
{activeTeamInfo && currentStep !== 'name'
? activeTeamInfo.name
: 'New workspace'}
: ''}
</Text>

<IconButton
name="cross"
variant="square"
@@ -71,16 +66,14 @@ const NewTeam: React.FC<NewTeamProps> = ({ step, hasNextStep, onClose }) => {
</Stack>
</Element>
<Stack
css={css({
flex: 1,
})}
css={{ flex: 1 }}
align="center"
direction="vertical"
justify="center"
>
{
{
create: <TeamCreate onComplete={handleStepCompletion} />,
name: <TeamName onComplete={handleStepCompletion} />,
members: (
<TeamMembers
hideSkip={!nextStep}
2 changes: 1 addition & 1 deletion packages/app/src/app/pages/Dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -108,7 +108,7 @@ export const Dashboard: FunctionComponent = () => {
const searchParams = new URLSearchParams(location.search);

if (JSON.parse(searchParams.get('new_workspace'))) {
actions.openCreateTeamModal({ step: 'members' });
actions.openCreateTeamModal({ step: 'name' });
searchParams.delete('new_workspace');
} else if (JSON.parse(searchParams.get('import_repo'))) {
actions.openCreateSandboxModal({ initialTab: 'import' });
39 changes: 33 additions & 6 deletions packages/app/src/app/pages/Pro/Create.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { useAppState, useActions } from 'app/overmind';
import { useAppState, useActions, useEffects } from 'app/overmind';
import {
ThemeProvider,
Stack,
@@ -17,16 +17,30 @@ import {
ORGANIZATION_CONTACT_LINK,
} from 'app/constants';
import { usePriceCalculation } from 'app/hooks/usePriceCalculation';
import { useCreateCheckout } from 'app/hooks';
import { dashboard as dashboardURLs } from '@codesandbox/common/lib/utils/url-generator';
import { SubscriptionCard } from './components/SubscriptionCard';
import type { CTA } from './components/SubscriptionCard';
import { PricingTable } from './components/PricingTable';
import { StyledPricingDetailsText } from './components/elements';
import { NewTeamModal } from '../Dashboard/Components/NewTeamModal';

export const ProCreate = () => {
const { hasLoadedApp, isLoggedIn, userCanStartTrial } = useAppState();
const { hasLoadedApp, isLoggedIn, userCanStartTrial, user } = useAppState();
const actions = useActions();
const effects = useEffects();
const [isLoading, setIsLoading] = useState(false);
const [checkout, createCheckout] = useCreateCheckout();

const { openCreateTeamModal } = useActions();
useEffect(() => {
if (checkout.status === 'error') {
setIsLoading(false);

effects.notificationToast.error(
`Could not create stripe checkout link. ${checkout.error}`
);
}
}, [checkout]);

const oneSeatPrice = usePriceCalculation({
billingInterval: 'year',
@@ -42,9 +56,22 @@ export const ProCreate = () => {

const newWorkspaceCTA: CTA = {
text: userCanStartTrial ? 'Start trial' : 'Upgrade to Pro',
isLoading,
variant: 'dark',
onClick: () => {
openCreateTeamModal();
onClick: async () => {
setIsLoading(true);
const newTeam = await actions.dashboard.createTeam({
teamName: `${user.username}'s pro`,
});

await createCheckout({
team_id: newTeam.id,
success_path: dashboardURLs.recent(newTeam.id, {
new_workspace: 'true',
}),
utm_source: 'pro_page',
});
setIsLoading(false);
},
};

4 changes: 2 additions & 2 deletions packages/sandbox-hooks/console/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dispatch, listen, iframeHandshake } from 'codesandbox-api';
import Hook from 'csb-console-feed/lib/Hook';
import { Encode } from 'csb-console-feed/lib/Transform';
import Hook from 'console-feed/lib/Hook';
import { Encode } from 'console-feed/lib/Transform';

export default function setupConsole() {
Hook(window.console, async log => {
2 changes: 1 addition & 1 deletion packages/sandbox-hooks/package.json
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@
"dependencies": {
"@codesandbox/common": "^1.0.8",
"codesandbox-api": "0.0.32",
"csb-console-feed": "^3.7.0",
"console-feed": "^3.1.9",
"css-line-break": "^1.1.1",
"react-dev-utils": "3.1.1"
},
22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -10544,6 +10544,17 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==

console-feed@^3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/console-feed/-/console-feed-3.1.9.tgz#946191e5bf7a10fcabb478703129bc8b9eef5a9e"
integrity sha512-GAJCSTEvU3uZl4xVmYm7FONqzKelOj/+/+HZREb2w9CiOCjXRJtRP3nKiBYK8GszIDExiODEyz1WxWl2hHFkow==
dependencies:
"@emotion/core" "^10.0.10"
"@emotion/styled" "^10.0.12"
emotion-theming "^10.0.10"
linkifyjs "^2.1.6"
react-inspector "^5.1.0"

console-stream@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/console-stream/-/console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44"
@@ -11086,17 +11097,6 @@ crypto-random-string@^1.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=

csb-console-feed@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/csb-console-feed/-/csb-console-feed-3.7.0.tgz#909ea890d2c9e180143d409d5f88777bb9d0d1d5"
integrity sha512-pShlpxC8/hxJw67kLRbaewpeDMFAt1wXkqqyipce1HpH+R5wLqP3YhA4by1mIIMLQz2N58cYRa4EL9iU0jra8Q==
dependencies:
"@emotion/core" "^10.0.10"
"@emotion/styled" "^10.0.12"
emotion-theming "^10.0.10"
linkifyjs "^2.1.6"
react-inspector "^5.1.0"

css-animation@^1.3.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/css-animation/-/css-animation-1.4.1.tgz#5b8813125de0fbbbb0bbe1b472ae84221469b7a8"