[8/n][project settings ui] config domains and react dom context (#32)

This commit is contained in:
Vivian Phung 2024-05-14 16:09:33 -04:00 committed by GitHub
commit de6d5c302b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 170 additions and 86 deletions

View File

@ -17,6 +17,7 @@ const config: StorybookConfig = {
getAbsolutePath('@storybook/addon-essentials'), getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@chromatic-com/storybook'), getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-interactions'), getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('storybook-addon-remix-react-router'),
], ],
framework: { framework: {
name: getAbsolutePath('@storybook/react-vite'), name: getAbsolutePath('@storybook/react-vite'),

View File

@ -97,6 +97,7 @@
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.1.0", "prettier": "^3.1.0",
"storybook": "^8.0.10", "storybook": "^8.0.10",
"storybook-addon-remix-react-router": "^3.0.0",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.2.0" "vite": "^5.2.0"

View File

@ -1,12 +1,10 @@
import { useState } from 'react'; import { useState } from 'react';
import { import { Typography } from '@snowballtools/material-tailwind-react-fork';
Button,
Typography,
} from '@snowballtools/material-tailwind-react-fork';
import { GitRepositoryDetails } from '../../../../types/types'; import { GitRepositoryDetails } from '../../../../types';
import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog'; import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog';
import { Button } from 'components/shared/Button';
const RepoConnectedSection = ({ const RepoConnectedSection = ({
linkedRepo, linkedRepo,
@ -24,12 +22,8 @@ const RepoConnectedSection = ({
<Typography variant="small">Connected just now</Typography> <Typography variant="small">Connected just now</Typography>
</div> </div>
<div> <div>
<Button <Button onClick={() => setDisconnectRepoDialogOpen(true)} size="sm">
onClick={() => setDisconnectRepoDialogOpen(true)} Disconnect
variant="outlined"
size="sm"
>
^ Disconnect
</Button> </Button>
</div> </div>
<DisconnectRepositoryDialog <DisconnectRepositoryDialog

View File

@ -2,13 +2,16 @@ import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { import { Heading } from 'components/shared/Heading';
Radio, import { InlineNotification } from 'components/shared/InlineNotification';
Typography, import { Input } from 'components/shared/Input';
Button, import { Button } from 'components/shared/Button';
Input, import { Radio } from 'components/shared/Radio';
Alert,
} from '@snowballtools/material-tailwind-react-fork'; interface SetupDomainFormValues {
domainName: string;
isWWW: string;
}
const SetupDomain = () => { const SetupDomain = () => {
const { const {
@ -17,32 +20,38 @@ const SetupDomain = () => {
formState: { isValid }, formState: { isValid },
watch, watch,
setValue, setValue,
} = useForm({ } = useForm<SetupDomainFormValues>({
defaultValues: { defaultValues: {
domainName: '', domainName: '',
isWWW: 'false', isWWW: 'false',
}, },
mode: 'onChange',
}); });
const [domainStr, setDomainStr] = useState(''); const [domainStr, setDomainStr] = useState<string>('');
const navigate = useNavigate(); const navigate = useNavigate();
const isWWWRadioOptions = [
{ label: domainStr, value: 'false' },
{ label: `www.${domainStr}`, value: 'true' },
];
useEffect(() => { useEffect(() => {
const subscription = watch((value, { name }) => { const subscription = watch((value, { name }) => {
if (name === 'domainName' && value.domainName) { if (name === 'domainName' && value.domainName) {
const domainArr = value.domainName.split('www.'); const domainArr = value.domainName.split('www.');
setDomainStr(domainArr.length > 1 ? domainArr[1] : domainArr[0]); const cleanedDomain =
domainArr.length > 1 ? domainArr[1] : domainArr[0];
setDomainStr(cleanedDomain);
if (value.domainName.startsWith('www.')) { setValue(
setValue('isWWW', 'true'); 'isWWW',
} else { value.domainName.startsWith('www.') ? 'true' : 'false',
setValue('isWWW', 'false'); );
}
} }
}); });
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [watch]); }, [watch, setValue]);
return ( return (
<form <form
@ -54,60 +63,49 @@ const SetupDomain = () => {
className="flex flex-col gap-6 w-full" className="flex flex-col gap-6 w-full"
> >
<div> <div>
<Typography variant="h5">Setup domain name</Typography> <Heading className="text-sky-950 text-lg font-medium leading-normal">
<Typography variant="small"> Setup domain name
</Heading>
<p className="text-slate-500 text-sm font-normal leading-tight">
Add your domain and setup redirects Add your domain and setup redirects
</Typography> </p>
</div> </div>
<div className="w-auto">
<Typography variant="small">Domain name</Typography>
<Input <Input
type="text" size="md"
variant="outlined" placeholder="example.com"
size="lg"
className="w-full"
{...register('domainName', { {...register('domainName', {
required: true, required: true,
})} })}
label="Domain name"
/> />
</div>
{isValid && ( {isValid && (
<div> <div className="self-stretch flex flex-col gap-4">
<Typography>Primary domain</Typography> <Heading className="text-sky-950 text-lg font-medium leading-normal">
<div className="flex flex-col gap-3"> Primary domain
</Heading>
<Radio <Radio
label={domainStr} options={isWWWRadioOptions}
{...register('isWWW')} onValueChange={(value) => setValue('isWWW', value)}
value="false" value={watch('isWWW')}
type="radio"
/> />
<Radio <InlineNotification
label={`www.${domainStr}`} variant="info"
{...register('isWWW')} title={`For simplicity, we'll redirect the ${
value="true" watch('isWWW') === 'true'
type="radio"
/>
</div>
<Alert color="blue">
<i>^</i> For simplicity, well redirect the{' '}
{watch('isWWW') === 'true'
? `non-www variant to www.${domainStr}` ? `non-www variant to www.${domainStr}`
: `www variant to ${domainStr}`} : `www variant to ${domainStr}`
. Redirect preferences can be changed later }. Redirect preferences can be changed later`}
</Alert> />
</div> </div>
)} )}
<Button <div className="self-stretch">
disabled={!isValid} <Button disabled={!isValid} type="submit">
className="w-fit" Next
color={isValid ? 'blue' : 'gray'}
type="submit"
>
<i>^</i> Next
</Button> </Button>
</div>
</form> </form>
); );
}; };

View File

@ -1,8 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog'; import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
interface WebhookCardProps { interface WebhookCardProps {
webhookUrl: string; webhookUrl: string;
@ -10,6 +10,8 @@ interface WebhookCardProps {
} }
const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => { const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
const { toast, dismiss } = useToast();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
return ( return (
<div className="flex justify-between w-full mb-3"> <div className="flex justify-between w-full mb-3">
@ -19,10 +21,15 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
size="sm" size="sm"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(webhookUrl); navigator.clipboard.writeText(webhookUrl);
toast.success('Copied to clipboard'); toast({
id: 'webhook_copied',
title: 'Webhook copied to clipboard',
variant: 'success',
onDismiss: dismiss,
});
}} }}
> >
C Copy
</Button> </Button>
<Button <Button
size="sm" size="sm"

View File

@ -1,13 +1,12 @@
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import {
Alert,
Button,
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from '../../../../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../../../context/GQLClientContext';
import { Heading } from 'components/shared/Heading'; import { Heading } from 'components/shared/Heading';
import { Table } from 'components/shared/Table'; import { Table } from 'components/shared/Table';
import { Button } from 'components/shared/Button';
import { InlineNotification } from 'components/shared/InlineNotification';
import { ArrowRightCircleIcon } from 'components/shared/CustomIcon';
const Config = () => { const Config = () => {
const { id, orgSlug } = useParams(); const { id, orgSlug } = useParams();
@ -78,12 +77,18 @@ const Config = () => {
</Table.Body> </Table.Body>
</Table> </Table>
<Alert color="blue"> <InlineNotification
<i>^</i>It can take up to 48 hours for these updates to reflect variant="info"
globally. title={`It can take up to 48 hours for these updates to reflect
</Alert> globally.`}
<Button className="w-fit" color="blue" onClick={handleSubmitDomain}> />
Finish <i>{'>'}</i> <Button
className="w-fit"
onClick={handleSubmitDomain}
variant="primary"
rightIcon={<ArrowRightCircleIcon />}
>
Finish
</Button> </Button>
</div> </div>
); );

View File

@ -0,0 +1,30 @@
import { Meta, StoryObj } from '@storybook/react';
import {
reactRouterParameters,
withRouter,
} from 'storybook-addon-remix-react-router';
import Config from '../../../pages/org-slug/projects/id/settings/domains/add/Config';
const meta: Meta<typeof Config> = {
title: 'Project/Settings/Config',
component: Config,
tags: ['autodocs'],
decorators: [withRouter],
parameters: {
reactRouter: reactRouterParameters({
location: {
pathParams: { userId: 'me' },
},
routing: {
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains/add/config',
},
}),
},
} as Meta<typeof Config>;
export default meta;
type Story = StoryObj<typeof Config>;
export const Default: Story = {};

View File

@ -0,0 +1,30 @@
import { Meta, StoryObj } from '@storybook/react';
import SetupDomain from 'components/projects/project/settings/SetupDomain';
import {
reactRouterParameters,
withRouter,
} from 'storybook-addon-remix-react-router';
const meta: Meta<typeof SetupDomain> = {
title: 'Project/Settings/SetupDomain',
component: SetupDomain,
tags: ['autodocs'],
decorators: [withRouter],
parameters: {
reactRouter: reactRouterParameters({
location: {
pathParams: { userId: 'me' },
},
routing: {
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains/add',
},
}),
},
} as Meta<typeof SetupDomain>;
export default meta;
type Story = StoryObj<typeof SetupDomain>;
export const Default: Story = {};

View File

@ -8530,6 +8530,11 @@ compare-func@^2.0.0:
array-ify "^1.0.0" array-ify "^1.0.0"
dot-prop "^5.1.0" dot-prop "^5.1.0"
compare-versions@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a"
integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==
compressible@~2.0.16: compressible@~2.0.16:
version "2.0.18" version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@ -14701,6 +14706,11 @@ react-hot-toast@^2.4.1:
dependencies: dependencies:
goober "^2.1.10" goober "^2.1.10"
react-inspector@6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d"
integrity sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==
react-is@18.1.0: react-is@18.1.0:
version "18.1.0" version "18.1.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67"
@ -15764,6 +15774,14 @@ store2@^2.14.2:
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.3.tgz#24077d7ba110711864e4f691d2af941ec533deb5" resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.3.tgz#24077d7ba110711864e4f691d2af941ec533deb5"
integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg== integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==
storybook-addon-remix-react-router@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/storybook-addon-remix-react-router/-/storybook-addon-remix-react-router-3.0.0.tgz#e31e3b1ba51481de7c3daaac9f43f72e9c4f00bc"
integrity sha512-0D7VDVf6uX6vgegpCb3v1/TIADxRWomycyj0ZNuVjrCO6w6FwfZ9CHlCK7k9v6CB2uqKjPiaBwmT7odHyy1qYA==
dependencies:
compare-versions "^6.0.0"
react-inspector "6.0.2"
storybook@^8.0.10: storybook@^8.0.10:
version "8.0.10" version "8.0.10"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.0.10.tgz#397e7a95641421610ba4741bc63adbb380eed01f" resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.0.10.tgz#397e7a95641421610ba4741bc63adbb380eed01f"