@chakra-ui/react#ModalFooter TypeScript Examples
The following examples show how to use
@chakra-ui/react#ModalFooter.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Modal.tsx From calories-in with MIT License | 6 votes |
function Modal({ isOpen, onClose }: Props) {
const selectRef = useRef<HTMLSelectElement>(null)
return (
<ModalBase isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Filters</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Content selectRef={selectRef} />
</ModalBody>
<ModalFooter>
<Footer onClose={onClose} />
</ModalFooter>
</ModalContent>
</ModalBase>
)
}
Example #2
Source File: MissingFoodsModal.tsx From calories-in with MIT License | 6 votes |
function MissingFoodsModal({ isOpen, onClose, onImport }: Props) {
return (
<Modal isOpen={isOpen} onClose={onClose} size="md" isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>Missing foods</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text fontWeight="medium">
The meal plan you contains foods that are not part of your list.
</Text>
<br />
<Text>
You can try to import the missing foods or continue without them.
</Text>
</ModalBody>
<ModalFooter>
<Button onClick={onClose} mr={3}>
Continue
</Button>
<Button
variant="solid"
colorScheme="teal"
onClick={() => {
onImport()
onClose()
}}
>
Import foods
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
Example #3
Source File: Footer.tsx From calories-in with MIT License | 6 votes |
function Footer({ onClose, onSubmit, isEditing }: Props) {
return (
<ModalFooter>
<HStack spacing={3}>
<Button onClick={onClose}>Close</Button>
{isEditing && (
<Button
colorScheme="teal"
type="submit"
variant="solid"
onClick={onSubmit}
>
Save food
</Button>
)}
</HStack>
</ModalFooter>
)
}
Example #4
Source File: DeleteConfirmationModal.tsx From calories-in with MIT License | 6 votes |
function DeleteConfirmationModal({
isOpen,
onCancel,
onConfirm,
text,
confirmButtonLabel,
}: Props) {
return (
<Modal isOpen={isOpen} onClose={onCancel} size="xs" isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>Delete food</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>{text}</Text>
<br />
<Text fontWeight="medium">This action cannot be undone.</Text>
</ModalBody>
<ModalFooter>
<Button variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button colorScheme="red" ml={3} onClick={onConfirm}>
{confirmButtonLabel}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
Example #5
Source File: Content.tsx From calories-in with MIT License | 5 votes |
function Content({ title, onClose, initialRef, variantFormIndex }: Props) {
const { register } = useFormContext()
const nameRegister = register('name')
const nameInputRef = useMergeRefs(nameRegister.ref, initialRef)
const onSubmit = useSubmitVariantNameForm({
variantFormIndex,
onComplete: onClose,
})
const { errorMessage, isInvalid } = useFormError('name')
useSelectInputText(initialRef)
return (
<form onSubmit={onSubmit}>
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl isInvalid={isInvalid}>
<FormLabel>Name</FormLabel>
<Input
autoComplete="off"
{...nameRegister}
ref={nameInputRef}
focusBorderColor={isInvalid ? 'red.500' : undefined}
placeholder="Enter name"
/>
<Collapse animateOpacity={true} in={Boolean(errorMessage)}>
<Box minHeight="21px">
<FormErrorMessage>{errorMessage}</FormErrorMessage>
</Box>
</Collapse>
</FormControl>
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button
type="submit"
colorScheme="teal"
variant="solid"
onClick={onSubmit}
>
Rename
</Button>
</ModalFooter>
</ModalContent>
</form>
)
}
Example #6
Source File: index.tsx From calories-in with MIT License | 5 votes |
function Content({ onClose, initialVariantForm }: Props) {
const dietForm = useDietForm()
const { variantsForms } = dietForm
const getDietFormStatsTree = useGetDietFormStatsTree()
const dietFormStatsTree = getDietFormStatsTree(dietForm)
const initialVariantStatsTree = dietFormStatsTree.subtrees.find(
(statsTree: StatsTree) => statsTree.id === initialVariantForm.fieldId
)
if (!initialVariantStatsTree) {
throw new Error()
}
return (
<ModalContent>
<ModalHeader>Day Details</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VariantsDetailsFormProvider
initialVariantForm={initialVariantForm}
initialVariantStats={initialVariantStatsTree.stats}
>
<form>
<FormFields
initialVariantForm={initialVariantForm}
canEdit={false}
variantsForms={variantsForms}
dietFormStatsTree={dietFormStatsTree}
/>
</form>
</VariantsDetailsFormProvider>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>
</ModalFooter>
</ModalContent>
)
}
Example #7
Source File: index.tsx From calories-in with MIT License | 5 votes |
function Form({
ownerName,
notes,
onClose,
initialRef,
onEditNotes,
fieldId,
textAreaHeight,
}: Props) {
const { register, handleSubmit } = useFormContext()
const notesRegister = register('notes')
const notesInputRef = useMergeRefs(notesRegister.ref, initialRef)
const oneTimeCheckActions = useOneTimeCheckActions()
const onSubmit = handleSubmit((form: NotesForm) => {
oneTimeCheckActions.set(`notes-${fieldId}`)
onEditNotes(form.notes || undefined)
onClose()
})
const { errorMessage, isInvalid } = useFormError('name')
return (
<form onSubmit={onSubmit}>
<ModalContent>
<Header ownerName={ownerName} notes={notes} />
<ModalCloseButton />
<ModalBody>
<FormControl isInvalid={isInvalid}>
<FormLabel>Notes</FormLabel>
<Textarea
autoComplete="off"
{...notesRegister}
ref={notesInputRef}
focusBorderColor={isInvalid ? 'red.500' : undefined}
placeholder="Enter notes"
height={textAreaHeight}
/>
<Collapse animateOpacity={true} in={Boolean(errorMessage)}>
<Box minHeight="21px">
<FormErrorMessage>{errorMessage}</FormErrorMessage>
</Box>
</Collapse>
</FormControl>
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button
type="submit"
colorScheme="teal"
variant="solid"
onClick={onSubmit}
>
Save
</Button>
</ModalFooter>
</ModalContent>
</form>
)
}
Example #8
Source File: Content.tsx From calories-in with MIT License | 5 votes |
function Content({ onClose, title, onImport, action }: Props) {
const { allFoods } = useFoods()
const [blob] = useState(() => {
const allFoodsString = JSON.stringify(allFoods)
return new Blob([allFoodsString])
})
return (
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FoodsList
allowsFiltering={false}
height="350px"
itemUsageType="nonInteractive"
/>
</ModalBody>
<ModalFooter>
<HStack spacing={3}>
<Button onClick={onClose}>Close</Button>
{action === 'import' ? (
<Button
isDisabled={allFoods.length === 0}
variant="solid"
colorScheme="teal"
onClick={onImport}
>
{allFoods.length > 0
? `Import ${allFoods.length} ${
allFoods.length === 1 ? 'food' : 'foods'
}`
: 'Import'}
</Button>
) : (
<DownloadButton
blob={blob}
onClose={onClose}
fileName={getUntitledFileName({ prefix: 'foods' })}
label="Export"
isDisabled={allFoods.length === 0}
/>
)}
</HStack>
</ModalFooter>
</ModalContent>
)
}
Example #9
Source File: ShareButtonModal.tsx From coindrop with GNU General Public License v3.0 | 5 votes |
ShareButtonModal: FunctionComponent<Props> = ({ buttonColor }) => {
const { isOpen, onClose, onOpen } = useDisclosure();
const { query: { piggybankName: piggybankNameQuery }} = useRouter();
const piggybankName = Array.isArray(piggybankNameQuery) ? piggybankNameQuery[0] : piggybankNameQuery;
const publicUrl = `coindrop.to/${piggybankName}`;
const fullPublicUrl = `https://${publicUrl}`;
return (
<>
<Button
leftIcon={<ShareIcon />}
onClick={onOpen}
colorScheme={buttonColor}
isDisabled={isOpen}
>
Share
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<Heading
mt={4}
as="h2"
size="md"
mx={12}
textAlign="center"
>
{publicUrl}
</Heading>
<ModalBody>
<Box mb={4}>
<Box>
<Heading as="h2" size="lg">
Link
</Heading>
</Box>
<Center mt={2}>
<CopyLinkShareButton textToCopy={publicUrl} />
</Center>
</Box>
<ShareEmbedButton
fullPublicUrl={fullPublicUrl}
/>
<PiggybankQRCode
fullPublicUrl={fullPublicUrl}
publicUrl={publicUrl}
/>
<TipCards />
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
</>
);
}
Example #10
Source File: index.tsx From calories-in with MIT License | 5 votes |
function Content({ onClose }: Props) {
const [blob, setBlob] = useState<Blob>()
const [url, setUrl] = useState<string>()
const dietForm = useDietForm()
const onUpdate = useCallback((blob: Blob, url: string) => {
setBlob(blob)
setUrl(url)
}, [])
function onViewInBrowser() {
window.open(url, '_blank')
}
return (
<ModalContent>
<ModalHeader fontWeight="normal">
Export{' '}
<Text as="span" fontWeight="bold">
{dietForm.name}
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody px={0}>
<Exporter onUpdate={onUpdate} />
</ModalBody>
<ModalFooter>
<VStack spacing={3} width="100%">
{blob && url && (
<DownloadButton
blob={blob}
onClose={onClose}
label="Download"
isFullWidth={true}
fileName={dietForm.name}
isLoading={blob === undefined}
/>
)}
{blob && url && (
<Button
mr={3}
variant="outline"
colorScheme="teal"
onClick={onViewInBrowser}
isFullWidth={true}
>
View in browser
</Button>
)}
<Button isFullWidth={true} variant="solid" onClick={onClose}>
Close
</Button>
</VStack>
</ModalFooter>
</ModalContent>
)
}
Example #11
Source File: About.tsx From calories-in with MIT License | 4 votes |
function About({ isOpen, onClose }: Props) {
function onContact() {
window.location.href = 'mailto:[email protected]'
}
return (
<Modal isOpen={isOpen} onClose={onClose} size="2xl" scrollBehavior="inside">
<ModalOverlay />
<ModalContent>
<ModalHeader>About </ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text fontSize="lg">
<Text>Hi, I'm Vladimir, the person behind this project.</Text>
<br />
<Text>
<Text fontWeight="semibold" as="span" textColor="teal.600">
Calories-In
</Text>{' '}
is made for people who follow meal plans that involve preparing
everything by yourself and gives them full control to fine tune
the nutritional values.
</Text>
<br />
<Text>
The idea was born out of my experience of trying to find a better
alternative to Google Sheets for calculating the macros of my own
meal plans. I wanted to be able to do this on desktop as it's more
convenient but nothing really felt fast and simple enough.
</Text>
<br />
<Text>The main differences to other apps in this space are:</Text>
<br />
<List ml={8}>
<ListItem>
<ListIcon as={CheckCircle} color="teal.600" />
<Text fontWeight="semibold" as="span" textColor="teal.600">
Faster search
</Text>{' '}
: There are actually not that many foods you need when you
prepare everything yourself. This means all of the food data can
be downloaded beforehand which makes the search super fast. Of
course you can add your own foods if you'd like.{' '}
</ListItem>
<br />
<ListItem>
<ListIcon as={CheckCircle} color="teal.600" />
<Text fontWeight="semibold" as="span" textColor="teal.600">
Undo/Redo
</Text>{' '}
: Building a plan from scratch or updating an existing one
involves some back and forth choosing the right foods and
adjusting their amounts. This is especially true if you want to
be as close as possible to a specific calorie limit and have
your macros be a certain percentages split.
</ListItem>
<br />
<ListItem>
<ListIcon as={CheckCircle} color="teal.600" />
<Text fontWeight="semibold" as="span" textColor="teal.600">
Faster export
</Text>{' '}
: Creating the PDF file for your meal plan is done entirely
inside the browser. It does not involve generating and
downloading it from a server. This means I can keep the cost of
running the website low and you get your file in just a few
seconds.
</ListItem>
<br />
<ListItem>
<ListIcon as={CheckCircle} color="teal.600" />
<Text fontWeight="semibold" as="span" textColor="teal.600">
Simpler
</Text>{' '}
: There are no other pages except the editor. Most of the other
tools are bloated with additional features for professionals,
such as managing clients, creating invoices, etc.
</ListItem>
<br />
<ListItem>
<ListIcon as={CheckCircle} color="teal.600" />
<Text fontWeight="semibold" as="span" textColor="teal.600">
Fully mobile
</Text>{' '}
: You can use your phone or tablet to build your meal plans
right from your browser. If you add the app to your home screen
it will look and feel almost like a native one.
</ListItem>
</List>
<Text>
<br />
Let me know if you found it useful or have any comments in
general:
</Text>
<br />
<Button size="lg" colorScheme="teal" onClick={onContact}>
Contact me directly
</Button>
<br />
<br />
<Text>No email will go unanswered, I promise :)</Text>
</Text>
</ModalBody>
<ModalFooter>
<Button size="lg" onClick={onClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
Example #12
Source File: note-form.tsx From notebook with MIT License | 4 votes |
NoteForm: React.SFC<NoteFormProps> = ({
isOpen,
onClose,
selectedNote,
handleNoteCreate,
handleNoteUpdate
}) => {
const { register, handleSubmit, formState, errors } = useForm<FormInputs>({
mode: "onChange"
});
const onSubmit: SubmitHandler<FormInputs> = data => {
let newNote: note = {
id: "",
title: data.title,
body: data.body
};
if (handleNoteCreate) {
newNote.id = nanoid();
if (handleNoteCreate) handleNoteCreate(newNote);
} else {
newNote.id = selectedNote ? selectedNote.id : "";
if (handleNoteUpdate) handleNoteUpdate(newNote);
}
onClose();
};
const validateTitle = (value: string) => {
if (!value) {
return "Title is required";
} else return true;
};
const validateBody = (value: string) => {
if (!value) {
return "Body is required";
} else return true;
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="lg"
isCentered
motionPreset="slideInBottom"
>
<ModalOverlay />
<ModalContent>
<form onSubmit={handleSubmit(onSubmit)}>
<ModalHeader>{selectedNote ? "Edit" : "Create"} a Note</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl isInvalid={!!errors?.title} isRequired>
<FormLabel>Title</FormLabel>
<Input
name="title"
placeholder="Title"
defaultValue={selectedNote?.title}
ref={register({ validate: validateTitle })}
/>
<FormErrorMessage>
{!!errors?.title && errors?.title?.message}
</FormErrorMessage>
</FormControl>
<FormControl size="lg" mt={4} isInvalid={!!errors?.body} isRequired>
<FormLabel>Body</FormLabel>
<Textarea
name="body"
placeholder="Body"
size="md"
borderRadius="5px"
defaultValue={selectedNote?.body}
ref={register({ validate: validateBody })}
/>
<FormErrorMessage>
{!!errors?.body && errors?.body?.message}
</FormErrorMessage>
</FormControl>
</ModalBody>
<ModalFooter>
<Button
type="submit"
colorScheme="blue"
isLoading={formState.isSubmitting}
mr={3}
>
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</form>
</ModalContent>
</Modal>
);
}
Example #13
Source File: Inspector.tsx From openchakra with MIT License | 4 votes |
Inspector = () => {
const dispatch = useDispatch()
const component = useSelector(getSelectedComponent)
const { isOpen, onOpen, onClose } = useDisclosure()
const [componentName, onChangeComponentName] = useState('')
const componentsNames = useSelector(getComponentNames)
const { clearActiveProps } = useInspectorUpdate()
const saveComponent = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
dispatch.components.setComponentName({
componentId: component.id,
name: componentName,
})
onClose()
onChangeComponentName('')
}
const isValidComponentName = useMemo(() => {
return (
!!componentName.match(/^[A-Z]\w*$/g) &&
!componentsNames.includes(componentName) &&
// @ts-ignore
!componentsList.includes(componentName)
)
}, [componentName, componentsNames])
const { type, rootParentType, id, children } = component
const isRoot = id === 'root'
const parentIsRoot = component.parent === 'root'
const docType = rootParentType || type
const componentHasChildren = children.length > 0
useEffect(() => {
clearActiveProps()
}, [clearActiveProps])
return (
<>
<Box bg="white">
<Box
fontWeight="semibold"
fontSize="md"
color="yellow.900"
py={2}
px={2}
boxShadow="sm"
bg="yellow.100"
display="flex"
justifyContent="space-between"
flexDir="column"
>
{isRoot ? 'Document' : type}
{!!component.componentName && (
<Text fontSize="xs" fontWeight="light">
{component.componentName}
</Text>
)}
</Box>
{!isRoot && (
<Stack
direction="row"
py={2}
spacing={2}
align="center"
zIndex={99}
px={2}
flexWrap="wrap"
justify="flex-end"
>
<CodeActionButton />
{!component.componentName && (
<ActionButton
label="Name component"
icon={<EditIcon path="" />}
onClick={onOpen}
/>
)}
<ActionButton
label="Duplicate"
onClick={() => dispatch.components.duplicate()}
icon={<CopyIcon path="" />}
/>
<ActionButton
label="Reset props"
icon={<IoMdRefresh />}
onClick={() => dispatch.components.resetProps(component.id)}
/>
<ActionButton
label="Chakra UI Doc"
as={Link}
onClick={() => {
window.open(
`https://chakra-ui.com/${docType.toLowerCase()}`,
'_blank',
)
}}
icon={<GoRepo />}
/>
<ActionButton
bg="red.500"
label="Remove"
onClick={() => dispatch.components.deleteComponent(component.id)}
icon={<FiTrash2 />}
/>
</Stack>
)}
</Box>
<Box pb={1} bg="white" px={3}>
<Panels component={component} isRoot={isRoot} />
</Box>
<StylesPanel
isRoot={isRoot}
showChildren={componentHasChildren}
parentIsRoot={parentIsRoot}
/>
<Modal onClose={onClose} isOpen={isOpen} isCentered>
<ModalOverlay>
<ModalContent>
<form onSubmit={saveComponent}>
<ModalHeader>Save this component</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl isInvalid={!isValidComponentName}>
<FormLabel>Component name</FormLabel>
<Input
size="md"
autoFocus
variant="outline"
isFullWidth
focusBorderColor="blue.500"
errorBorderColor="red.500"
value={componentName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChangeComponentName(e.target.value)
}
/>
{!isValidComponentName && (
<FormErrorMessage>
Component name must start with a capital character and
must not contain space or special character, and name
should not be already taken (including existing chakra-ui
components).
</FormErrorMessage>
)}
<FormHelperText>
This will name your component that you will see in the code
panel as a separated component.
</FormHelperText>
</FormControl>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
type="submit"
isDisabled={!isValidComponentName}
>
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</form>
</ModalContent>
</ModalOverlay>
</Modal>
</>
)
}
Example #14
Source File: AccountModal.tsx From eth-dapps-nextjs-boiletplate with MIT License | 4 votes |
export default function AccountModal({ isOpen, onClose }: Props) {
const { globalState, dispatch } = useContext(globalContext)
const { account, provider } = globalState
const chainIdPaths = {
1: '', // mainnet
42: 'kovan.',
3: 'ropsten.',
4: 'rinkeby.',
5: 'goerli.',
}
const chainPath = provider && chainIdPaths[parseInt(provider.chainId)]
async function handleDeactivateAccount() {
//deactivate();
if (provider && !provider.isMetaMask) { // isWalletConnect then
await provider.disconnect()
}
dispatch({ type: 'CLEAR_STATE'})
onClose();
}
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered size="md">
<ModalOverlay />
<ModalContent
background="gray.900"
border="1px"
borderStyle="solid"
borderColor="gray.700"
borderRadius="3xl"
>
<ModalHeader color="white" px={4} fontSize="lg" fontWeight="medium">
Account
</ModalHeader>
<ModalCloseButton
color="white"
fontSize="sm"
_hover={{
color: "whiteAlpha.700",
}}
/>
<ModalBody pt={0} px={4}>
<Box
borderRadius="3xl"
border="1px"
borderStyle="solid"
borderColor="gray.600"
px={5}
pt={4}
pb={2}
mb={3}
>
<Flex justifyContent="space-between" alignItems="center" mb={3}>
<Text color="gray.400" fontSize="sm">
Connected with {provider?.isMetaMask ? 'MetaMask' : 'WalletConnect'}
</Text>
<Button
variant="outline"
size="sm"
borderColor="blue.800"
borderRadius="3xl"
color="blue.500"
fontSize="13px"
fontWeight="normal"
px={2}
height="26px"
_hover={{
background: "none",
borderColor: "blue.300",
textDecoration: "underline",
}}
onClick={handleDeactivateAccount}
>
Disconnect
</Button>
</Flex>
<Flex alignItems="center" mt={2} mb={4} lineHeight={1}>
<Identicon />
<Text
color="white"
fontSize="xl"
fontWeight="semibold"
ml="2"
lineHeight="1.1"
>
{account &&
`${account.slice(0, 6)}...${account.slice(
account.length - 4,
account.length
)}`}
</Text>
</Flex>
<Flex alignContent="center" m={3}>
<Button
variant="link"
color="gray.400"
fontWeight="normal"
fontSize="sm"
_hover={{
textDecoration: "none",
color: "whiteAlpha.800",
}}
>
<CopyIcon mr={1} />
Copy Address
</Button>
{
chainPath?
<Link
fontSize="sm"
display="flex"
alignItems="center"
href={`https://${chainPath}etherscan.io/address/${account}`}
isExternal
color="gray.400"
ml={6}
_hover={{
color: "whiteAlpha.800",
textDecoration: "underline",
}}
>
<ExternalLinkIcon mr={1} />
View on Explorer
</Link>:''
}
</Flex>
</Box>
</ModalBody>
<ModalFooter
justifyContent="end"
background="gray.700"
borderBottomLeftRadius="3xl"
borderBottomRightRadius="3xl"
p={6}
>
<Text
color="white"
textAlign="left"
fontWeight="medium"
fontSize="md"
>
Your transactions will appear here...
</Text>
</ModalFooter>
</ModalContent>
</Modal>
);
}
Example #15
Source File: create-app-github.tsx From ledokku with MIT License | 4 votes |
CreateAppGithub = () => {
const history = useHistory();
const toast = useToast();
const { user } = useAuth();
const { data: dataApps } = useAppsQuery();
const [isNewWindowClosed, setIsNewWindowClosed] = useState(false);
const [selectedRepo, setSelectedRepo] = useState<Repository>();
const [selectedBranch, setSelectedBranch] = useState('');
const [isProceedModalOpen, setIsProceedModalOpen] = useState(false);
const {
data: installationData,
loading: installationLoading,
} = useGithubInstallationIdQuery({ fetchPolicy: 'network-only' });
const [
getRepos,
{ data: reposData, loading: reposLoading },
] = useRepositoriesLazyQuery({ fetchPolicy: 'network-only' });
const [
getBranches,
{ data: branchesData, loading: branchesLoading },
] = useBranchesLazyQuery({ fetchPolicy: 'network-only' });
const [arrayOfCreateAppLogs, setArrayOfCreateAppLogs] = useState<
RealTimeLog[]
>([]);
const [isTerminalVisible, setIsTerminalVisible] = useState(false);
const [isToastShown, setIsToastShown] = useState(false);
const [createAppGithubMutation, { loading }] = useCreateAppGithubMutation();
const [
isAppCreationSuccess,
setIsAppCreationSuccess,
] = useState<AppCreationStatus>();
useAppCreateLogsSubscription({
onSubscriptionData: (data) => {
const logsExist = data.subscriptionData.data?.appCreateLogs;
if (logsExist) {
setArrayOfCreateAppLogs((currentLogs) => {
return [...currentLogs, logsExist];
});
if (logsExist.type === 'end:success') {
setIsAppCreationSuccess(AppCreationStatus.SUCCESS);
} else if (logsExist.type === 'end:failure') {
setIsAppCreationSuccess(AppCreationStatus.FAILURE);
}
}
},
});
const createAppGithubSchema = yup.object().shape({
name: yup
.string()
.required('App name is required')
.matches(/^[a-z0-9-]+$/)
.test(
'Name exists',
'App with this name already exists',
(val) => !dataApps?.apps.find((app) => app.name === val)
),
repo: yup.object({
fullName: yup.string().required(),
id: yup.string().required(),
name: yup.string().required(),
}),
installationId: yup.string().required(),
gitBranch: yup.string().optional(),
});
const formik = useFormik<{
name: string;
repo: {
fullName: string;
id: string;
name: string;
};
installationId: string;
gitBranch: string;
}>({
initialValues: {
name: '',
repo: {
fullName: '',
id: '',
name: '',
},
installationId: '',
gitBranch: '',
},
validateOnChange: true,
validationSchema: createAppGithubSchema,
onSubmit: async (values) => {
if (installationData) {
try {
await createAppGithubMutation({
variables: {
input: {
name: values.name,
gitRepoFullName: values.repo.fullName,
branchName: values.gitBranch,
gitRepoId: values.repo.id,
githubInstallationId: values.installationId,
},
},
});
setIsTerminalVisible(true);
} catch (error) {
error.message === 'Not Found'
? toast.error(`Repository : ${values.repo.fullName} not found`)
: toast.error(error.message);
}
}
},
});
const handleNext = () => {
setIsTerminalVisible(false);
const appId = arrayOfCreateAppLogs[arrayOfCreateAppLogs.length - 1].message;
history.push(`app/${appId}`, 'new');
trackGoal(trackingGoals.createAppGithub, 0);
};
const handleOpen = () => {
const newWindow = window.open(
`https://github.com/apps/${config.githubAppName}/installations/new`,
'Install App',
'resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=' +
window.screen.width +
', left=' +
window.screen.width +
', toolbar=0, menubar=0, status=0'
);
const timer = setInterval(async () => {
if (newWindow && newWindow.closed) {
setIsNewWindowClosed(true);
clearInterval(timer);
}
}, 100);
};
useEffect(() => {
if (!installationLoading && installationData && isNewWindowClosed) {
getRepos({
variables: {
installationId: installationData.githubInstallationId.id,
},
});
setIsNewWindowClosed(false);
}
}, [
installationData,
installationLoading,
isNewWindowClosed,
setIsNewWindowClosed,
getRepos,
]);
useEffect(() => {
if (
!installationLoading &&
installationData &&
!reposLoading &&
reposData &&
selectedRepo
) {
getBranches({
variables: {
installationId: installationData.githubInstallationId.id,
repositoryName: selectedRepo.name,
},
});
}
}, [
installationData,
installationLoading,
reposData,
reposLoading,
getBranches,
selectedRepo?.name,
selectedRepo,
]);
const handleChangeRepo = (active: RepoOption) => {
setSelectedRepo(active.value);
setSelectedBranch('');
if (installationData) {
formik.setValues({
name: active.value.name,
installationId: installationData?.githubInstallationId.id,
repo: {
fullName: active.value.fullName,
name: active.value.name,
id: active.value.id,
},
gitBranch: '',
});
}
};
const handleChangeBranch = (active: BranchOption) => {
setSelectedBranch(active.value.name);
formik.setFieldValue('gitBranch', active.value.name);
};
const repoOptions: RepoOption[] = [];
if (reposData && !reposLoading) {
reposData?.repositories.map((r) =>
repoOptions.push({ value: r, label: r.fullName })
);
}
let branchOptions: BranchOption[] = [];
if (branchesData && !branchesLoading) {
branchesData.branches.map((b) =>
branchOptions.push({ value: b, label: b.name })
);
}
useEffect(() => {
if (installationData && !installationLoading) {
getRepos({
variables: {
installationId: installationData?.githubInstallationId.id,
},
});
}
}, [installationLoading, getRepos, installationData]);
useEffect(() => {
if (selectedRepo && installationData) {
getBranches({
variables: {
installationId: installationData?.githubInstallationId.id,
repositoryName: selectedRepo.name,
},
});
}
}, [selectedRepo, getBranches, installationData]);
// Effect for app creation
useEffect(() => {
isAppCreationSuccess === AppCreationStatus.FAILURE && !isToastShown
? toast.error('Failed to create an app') && setIsToastShown(true)
: isAppCreationSuccess === AppCreationStatus.SUCCESS &&
!isToastShown &&
toast.success('App created successfully') &&
setIsToastShown(true);
}, [isToastShown, isAppCreationSuccess, toast]);
return (
<>
<HeaderContainer>
<Header />
</HeaderContainer>
<Container maxW="5xl" mt={10}>
{isTerminalVisible ? (
<>
<p className="mb-2 ">
Creating <b>{formik.values.name}</b> app from{' '}
<b>{formik.values.repo.name}</b>
</p>
<p className="text-gray-500 mb-2">
Creating app usually takes a couple of minutes. Breathe in,
breathe out, logs are about to appear below:
</p>
<Terminal className={'w-6/6'}>
{arrayOfCreateAppLogs.map((log) => (
<p
key={arrayOfCreateAppLogs.indexOf(log)}
className={'text-s leading-5'}
>
{log.message?.replaceAll('[1G', '')}
</p>
))}
</Terminal>
{!!isAppCreationSuccess &&
isAppCreationSuccess === AppCreationStatus.SUCCESS ? (
<div className="mt-12 flex justify-end">
<Button
onClick={() => handleNext()}
color="grey"
iconEnd={<FiArrowRight size={20} />}
>
Next
</Button>
</div>
) : !!isAppCreationSuccess &&
isAppCreationSuccess === AppCreationStatus.FAILURE ? (
<div className="mt-12 flex justify-start">
<Button
onClick={() => {
setIsTerminalVisible(false);
formik.resetForm();
}}
color="grey"
iconEnd={<FiArrowLeft size={20} />}
>
Back
</Button>
</div>
) : null}
</>
) : (
<>
<Heading as="h2" size="md">
Create a new GitHub application
</Heading>
{installationData &&
!installationLoading &&
reposData &&
!reposLoading ? (
<>
<Text color="gray.400">
When you push to Git, your application will be redeployed
automatically.
</Text>
<Grid
templateColumns={{
sm: 'repeat(1, 1fr)',
md: 'repeat(3, 1fr)',
}}
>
<GridItem colSpan={2}>
<Flex alignItems="center" mt="12">
<Avatar
size="sm"
name={user?.userName}
src={user?.avatarUrl}
/>
<Text ml="2" fontWeight="bold">
{user?.userName}
</Text>
</Flex>
<form onSubmit={formik.handleSubmit}>
<Box mt="8">
<FormLabel>Repository</FormLabel>
<Select
placeholder="Select repository"
isSearchable={false}
onChange={handleChangeRepo}
options={repoOptions}
/>
</Box>
<Text mt="1" color="gray.400" fontSize="sm">
Can't see your repo in the list?{' '}
<Link
onClick={() => setIsProceedModalOpen(true)}
textDecoration="underline"
>
Configure the GitHub app.
</Link>
</Text>
<Box mt="8">
<FormLabel>Branch to deploy</FormLabel>
<Select
placeholder="Select branch"
isSearchable={false}
disabled={
!branchesData ||
branchesLoading ||
reposLoading ||
!reposData
}
onChange={handleChangeBranch}
options={branchOptions}
/>
</Box>
<Box mt="8" display="flex" justifyContent="flex-end">
<Button
type="submit"
color="grey"
disabled={!selectedBranch || !selectedRepo}
isLoading={loading}
>
Create
</Button>
</Box>
</form>
</GridItem>
</Grid>
</>
) : !reposLoading && !installationLoading && !reposData ? (
<>
<Alert mb="4" mt="4" w="65%" status="info">
<AlertIcon />
<Box flex="1">
<AlertTitle>Set up repository permissions</AlertTitle>
<AlertDescription display="block">
First you will need to set up permissions for repositories
that you would like to use with Ledokku. Once that's done,
it's time to choose repo and branch that you would like to
create app from and off we go.
</AlertDescription>
</Box>
</Alert>
<Button
color="grey"
onClick={() => setIsProceedModalOpen(true)}
>
Set up permissions
</Button>
</>
) : (
<Spinner />
)}
</>
)}
<Modal
isOpen={isProceedModalOpen}
onClose={() => setIsProceedModalOpen(false)}
isCentered
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Github setup info</ModalHeader>
<ModalCloseButton />
<ModalBody>
New window is about to open. After you are done selecting github
repos, close the window and refresh page.
</ModalBody>
<ModalFooter>
<Button
color="grey"
variant="outline"
className="mr-3"
onClick={() => setIsProceedModalOpen(false)}
>
Cancel
</Button>
<Button
color="grey"
onClick={() => {
handleOpen();
setIsProceedModalOpen(false);
}}
>
Proceed
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Container>
</>
);
}
Example #16
Source File: AppProxyPorts.tsx From ledokku with MIT License | 4 votes |
AppProxyPorts = ({ appId }: AppProxyPortsProps) => {
const toast = useToast();
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<
false | AppProxyPort
>(false);
const {
data: appProxyPortsData,
loading: appProxyPortsLoading,
// TODO display error
// error: appProxyPortsError,
refetch: appProxyPortsRefetch,
} = useAppProxyPortsQuery({
variables: { appId },
notifyOnNetworkStatusChange: true,
});
const [
removeAppProxyPortMutation,
{ loading: removeAppPortLoading },
] = useRemoveAppProxyPortMutation();
const handleCloseModal = () => {
setIsDeleteModalOpen(false);
};
const handleRemovePort = async () => {
const proxyPort = isDeleteModalOpen;
if (!proxyPort) return;
try {
await removeAppProxyPortMutation({
variables: {
input: {
appId,
scheme: proxyPort.scheme,
host: proxyPort.host,
container: proxyPort.container,
},
},
});
await appProxyPortsRefetch();
setIsDeleteModalOpen(false);
toast.success('Port mapping deleted successfully');
} catch (error) {
toast.error(error.message);
}
};
return (
<>
<Box py="5">
<Heading as="h2" size="md">
Port Management
</Heading>
<Text fontSize="sm" color="gray.400">
The following ports are assigned to your app.
</Text>
</Box>
{appProxyPortsLoading ? (
<Text fontSize="sm" color="gray.400">
Loading...
</Text>
) : null}
{appProxyPortsData && appProxyPortsData.appProxyPorts.length > 0 ? (
<Table>
<Thead>
<Tr>
<Th>Scheme</Th>
<Th isNumeric>Host port</Th>
<Th isNumeric>Container port</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{appProxyPortsData.appProxyPorts.map((proxyPort, index) => (
<Tr key={index}>
<Td>{proxyPort.scheme}</Td>
<Td isNumeric>{proxyPort.host}</Td>
<Td isNumeric>{proxyPort.container}</Td>
<Td>
<Button
colorScheme="red"
variant="link"
size="sm"
onClick={() => setIsDeleteModalOpen(proxyPort)}
>
Delete
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
) : null}
<Modal isOpen={!!isDeleteModalOpen} onClose={handleCloseModal} isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>Delete port</ModalHeader>
<ModalCloseButton />
<ModalBody>
Are you sure, you want to delete this port mapping?
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={handleCloseModal}>
Cancel
</Button>
<Button
colorScheme="red"
isLoading={appProxyPortsLoading || removeAppPortLoading}
onClick={handleRemovePort}
>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Button
mt="3"
variant="outline"
size="sm"
onClick={() => setIsAddModalOpen(true)}
>
Add port mapping
</Button>
<AddAppProxyPorts
appId={appId}
appProxyPortsRefetch={appProxyPortsRefetch}
open={isAddModalOpen}
onClose={() => setIsAddModalOpen(false)}
/>
</>
);
}
Example #17
Source File: AddAppProxyPorts.tsx From ledokku with MIT License | 4 votes |
AddAppProxyPorts = ({
appId,
appProxyPortsRefetch,
open,
onClose,
}: AddAppProxyPortsProps) => {
const [addAppProxyPortMutation] = useAddAppProxyPortMutation();
const toast = useToast();
const formik = useFormik<{ host: string; container: string }>({
initialValues: {
host: '',
container: '',
},
validateOnChange: true,
validationSchema: createAppProxyPortSchema,
onSubmit: async (values) => {
try {
await addAppProxyPortMutation({
variables: {
input: {
appId,
host: values.host,
container: values.container,
},
},
});
await appProxyPortsRefetch();
toast.success('Port mapping created successfully');
onClose();
} catch (error) {
toast.error(error.message);
}
},
});
if (!open) {
return null;
}
return (
<Modal isOpen={open} onClose={onClose} isCentered size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Add port mapping</ModalHeader>
<ModalCloseButton />
<ModalBody>
<SimpleGrid columns={{ sm: 1, md: 2 }} spacing={3}>
<FormControl
id="host"
isInvalid={Boolean(formik.errors.host && formik.touched.host)}
>
<FormLabel>Host port:</FormLabel>
<Input
name="host"
value={formik.values.host}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<FormErrorMessage>{formik.errors.host}</FormErrorMessage>
</FormControl>
<FormControl
id="container"
isInvalid={Boolean(
formik.errors.container && formik.touched.container
)}
>
<FormLabel>Container port:</FormLabel>
<Input
name="container"
value={formik.values.container}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<FormErrorMessage>{formik.errors.container}</FormErrorMessage>
</FormControl>
</SimpleGrid>
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={onClose}>
Cancel
</Button>
<Button
colorScheme="red"
isLoading={formik.isSubmitting}
onClick={() => formik.handleSubmit()}
>
Create
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
Example #18
Source File: index.tsx From jsonschema-editor-react with Apache License 2.0 | 4 votes |
SchemaObject: React.FunctionComponent<SchemaObjectProps> = (
props: React.PropsWithChildren<SchemaObjectProps>
) => {
const { schemaState, isReadOnly } = props;
const schema = useState(schemaState);
const properties = useState(schema.properties);
const propertiesOrNull:
| State<{
[key: string]: JSONSchema7Definition;
}>
| undefined = properties.ornull;
const isReadOnlyState = useState(isReadOnly);
const onCloseAdvanced = (): void => {
localState.isAdvancedOpen.set(false);
};
const showadvanced = (item: string): void => {
localState.isAdvancedOpen.set(true);
localState.item.set(item);
};
const focusRef = React.createRef<HTMLElement>();
const localState = useState({
isAdvancedOpen: false,
item: "",
});
if (!propertiesOrNull) {
return <></>;
} else {
return (
<div className="object-style">
{propertiesOrNull?.keys?.map((name) => {
return (
<SchemaItem
key={String(name)}
itemStateProp={
propertiesOrNull.nested(name as string) as State<JSONSchema7>
}
parentStateProp={schema}
name={name as string}
showadvanced={showadvanced}
required={schema.required.value as string[]}
isReadOnly={isReadOnlyState}
/>
);
})}
<Modal
isOpen={localState.isAdvancedOpen.get()}
finalFocusRef={focusRef}
size="lg"
onClose={onCloseAdvanced}
>
<ModalOverlay />
<ModalContent>
<ModalHeader textAlign="center">
Advanced Schema Settings
</ModalHeader>
<ModalBody>
<AdvancedSettings
itemStateProp={
propertiesOrNull.nested(
localState.item.value as string
) as State<JSONSchema7>
}
/>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
variant="ghost"
mr={3}
onClick={onCloseAdvanced}
>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</div>
);
}
}
Example #19
Source File: index.tsx From jsonschema-editor-react with Apache License 2.0 | 4 votes |
SchemaArray: React.FunctionComponent<SchemaArrayProps> = (
props: React.PropsWithChildren<SchemaArrayProps>
) => {
const { schemaState, isReadOnly } = props;
const state = useState(schemaState.items as JSONSchema7);
const isReadOnlyState = useState(isReadOnly);
const { length } = state.path.filter((name) => name !== "properties");
const tagPaddingLeftStyle = {
paddingLeft: `${20 * (length + 1)}px`,
};
const onCloseAdvanced = (): void => {
localState.isAdvancedOpen.set(false);
};
const showadvanced = (): void => {
localState.isAdvancedOpen.set(true);
};
const focusRef = React.createRef<HTMLElement>();
const localState = useState({
isAdvancedOpen: false,
});
return (
<>
<Flex
direction="row"
wrap="nowrap"
className="array-item"
mt={2}
mr={5}
style={tagPaddingLeftStyle}
>
<Input
key="Items"
isDisabled
value="Items"
size="sm"
flexShrink={1}
margin={2}
variant="outline"
/>
<Checkbox isDisabled margin={2} colorScheme="blue" />
<Select
variant="outline"
isDisabled={isReadOnlyState.value}
value={state.type.value as JSONSchema7TypeName}
size="sm"
margin={2}
placeholder="Choose data type"
onChange={(evt: React.ChangeEvent<HTMLSelectElement>) => {
const newSchema = handleTypeChange(
evt.target.value as JSONSchema7TypeName,
false
);
state.set(newSchema as JSONSchema7);
}}
>
{SchemaTypes.map((item, index) => {
return (
<option key={String(index)} value={item}>
{item}
</option>
);
})}
</Select>
<Input
value={state.title.value}
isDisabled={isReadOnlyState.value}
size="sm"
margin={2}
variant="outline"
placeholder="Add Title"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => {
state.title.set(evt.target.value);
}}
/>
<Input
value={state.description.value}
isDisabled={isReadOnlyState.value}
size="sm"
margin={2}
variant="outline"
placeholder="Add Description"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => {
state.description.set(evt.target.value);
}}
/>
<Tooltip
hasArrow
aria-label="Advanced Settings"
label="Advanced Settings"
placement="top"
>
<IconButton
isRound
isDisabled={isReadOnlyState.value}
size="sm"
mt={2}
mb={2}
ml={1}
variant="link"
colorScheme="blue"
fontSize="16px"
icon={<FiSettings />}
aria-label="Advanced Settings"
onClick={() => {
showadvanced();
}}
/>
</Tooltip>
{state.type.value === "object" && (
<Tooltip
hasArrow
aria-label="Add Child Node"
label="Add Child Node"
placement="top"
>
<IconButton
isRound
isDisabled={isReadOnlyState.value}
size="sm"
mt={2}
mb={2}
mr={2}
variant="link"
colorScheme="green"
fontSize="16px"
icon={<IoIosAddCircleOutline />}
aria-label="Add Child Node"
onClick={() => {
const fieldName = `field_${random()}`;
(state.properties as State<{
[key: string]: JSONSchema7;
}>)[fieldName].set(getDefaultSchema(DataType.string));
}}
/>
</Tooltip>
)}
</Flex>
{state.type?.value === "object" && (
<SchemaObject isReadOnly={isReadOnlyState} schemaState={state} />
)}
{state.type?.value === "array" && (
<SchemaArray isReadOnly={isReadOnlyState} schemaState={state} />
)}
<Modal
isOpen={localState.isAdvancedOpen.get()}
finalFocusRef={focusRef}
size="lg"
onClose={onCloseAdvanced}
>
<ModalOverlay />
<ModalContent>
<ModalHeader textAlign="center">Advanced Schema Settings</ModalHeader>
<ModalBody>
<AdvancedSettings itemStateProp={state} />
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
variant="ghost"
mr={3}
onClick={onCloseAdvanced}
>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
}