forked from cerc-io/snowballtools-base
[8/n][project settings ui] config domains and react dom context (#32)
This commit is contained in:
commit
de6d5c302b
@ -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'),
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
<Input
|
||||||
<Typography variant="small">Domain name</Typography>
|
size="md"
|
||||||
<Input
|
placeholder="example.com"
|
||||||
type="text"
|
{...register('domainName', {
|
||||||
variant="outlined"
|
required: true,
|
||||||
size="lg"
|
})}
|
||||||
className="w-full"
|
label="Domain name"
|
||||||
{...register('domainName', {
|
/>
|
||||||
required: true,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</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
|
||||||
<Radio
|
</Heading>
|
||||||
label={domainStr}
|
<Radio
|
||||||
{...register('isWWW')}
|
options={isWWWRadioOptions}
|
||||||
value="false"
|
onValueChange={(value) => setValue('isWWW', value)}
|
||||||
type="radio"
|
value={watch('isWWW')}
|
||||||
/>
|
/>
|
||||||
<Radio
|
<InlineNotification
|
||||||
label={`www.${domainStr}`}
|
variant="info"
|
||||||
{...register('isWWW')}
|
title={`For simplicity, we'll redirect the ${
|
||||||
value="true"
|
watch('isWWW') === 'true'
|
||||||
type="radio"
|
? `non-www variant to www.${domainStr}`
|
||||||
/>
|
: `www variant to ${domainStr}`
|
||||||
</div>
|
}. Redirect preferences can be changed later`}
|
||||||
<Alert color="blue">
|
/>
|
||||||
<i>^</i> For simplicity, we’ll redirect the{' '}
|
|
||||||
{watch('isWWW') === 'true'
|
|
||||||
? `non-www variant to www.${domainStr}`
|
|
||||||
: `www variant to ${domainStr}`}
|
|
||||||
. 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'}
|
</Button>
|
||||||
type="submit"
|
</div>
|
||||||
>
|
|
||||||
<i>^</i> Next
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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 = {};
|
@ -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 = {};
|
18
yarn.lock
18
yarn.lock
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user