diff --git a/app/components/Modal.tsx b/app/components/Modal.tsx index 9f567ad..b330fce 100644 --- a/app/components/Modal.tsx +++ b/app/components/Modal.tsx @@ -7,6 +7,7 @@ */ import { Dialog, DialogDismiss, type DialogStore } from '@ariakit/react' +import { Button } from '@oxide/design-system' import cn from 'classnames' import Icon from '~/components/Icon' @@ -23,11 +24,17 @@ const Modal = ({ title, children, width = 'medium', + onSubmit, + isLoading = false, + disabled = false, }: { dialogStore: DialogStore title: string children: React.ReactElement width?: Width + onSubmit?: () => void + isLoading?: boolean + disabled?: boolean }) => { return ( <> @@ -49,6 +56,33 @@ const Modal = ({
{children}
+ + {onSubmit && ( + + )} ) diff --git a/app/components/NewRfdButton.tsx b/app/components/NewRfdButton.tsx index ab0a3e6..4fa3291 100644 --- a/app/components/NewRfdButton.tsx +++ b/app/components/NewRfdButton.tsx @@ -6,16 +6,20 @@ * Copyright Oxide Computer Company */ -import { useDialogStore } from '@ariakit/react' +import { useDialogStore, type DialogStore } from '@ariakit/react' +import cn from 'classnames' +import { useState } from 'react' +import { useFetcher } from 'react-router' import Icon from '~/components/Icon' import { useRootLoaderData } from '~/root' import Modal from './Modal' +import { TextInput } from './TextInput' const NewRfdButton = () => { const dialog = useDialogStore() - const newRfdNumber = useRootLoaderData().newRfdNumber + const { user } = useRootLoaderData() return ( <> @@ -26,40 +30,124 @@ const NewRfdButton = () => { - - <> -

- There is a prototype script in the rfd{' '} - - repository - - ,{' '} - - scripts/new.sh - - , that will create a new RFD when used like the code below. -

- -

- {newRfdNumber - ? 'The snippet below automatically updates to ensure the new RFD number is correct.' - : 'Replace the number below with the next free number'} -

-
-            
-              $
-              scripts/new.sh{' '}
-              {newRfdNumber ? newRfdNumber.toString().padStart(4, '0') : '0042'} "My title
-              here"
-            
-          
- -
+ ) } +const CreateRfdModal = ({ + data, + dialog, +}: { + data: { title: string; name: string; email: string } + dialog: DialogStore +}) => { + const [title, setTitle] = useState('') + const [name, setName] = useState('') + const [email, setEmail] = useState('') + + const { newRfdNumber } = useRootLoaderData() + const fetcher = useFetcher() + + const body = `:state: prediscussion +:discussion: +:authors: ${name || data.name} <${email || data.email}> + += RFD ${newRfdNumber} ${title || '{title}'} + +== Determinations +` + + const handleSubmit = () => { + fetcher.submit( + { title, body }, + { + method: 'post', + action: `/api/new-rfd`, + encType: 'application/json', + }, + ) + } + + const formDisabled = fetcher.state !== 'idle' + const isFormInvalid = + !title.trim() || !(name || data.name).trim() || !(email || data.email).trim() + const submitDisabled = formDisabled || isFormInvalid + + return ( + + +
+ setTitle(el.target.value)} + disabled={formDisabled} + required + /> +
+ setName(el.target.value)} + disabled={formDisabled} + className="w-1/3" + /> + setEmail(el.target.value)} + disabled={formDisabled} + className="w-2/3" + /> +
+
+ +
+          {body}
+          
+
+ {fetcher.state === 'idle' && + fetcher.data && + !fetcher.data.ok && + fetcher.data.message && ( +
+ {fetcher.data.message} +
+ )} +
+
+ ) +} + export default NewRfdButton diff --git a/app/components/TextInput.tsx b/app/components/TextInput.tsx new file mode 100644 index 0000000..bbd3d75 --- /dev/null +++ b/app/components/TextInput.tsx @@ -0,0 +1,41 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import cn from 'classnames' +import { forwardRef } from 'react' + +export type TextInputBaseProps = React.ComponentPropsWithRef<'input'> & { + disabled?: boolean + className?: string +} + +export const TextInput = forwardRef( + ({ type = 'text', className, disabled, ...fieldProps }, ref) => { + return ( +
+ +
+ ) + }, +) diff --git a/app/components/rfd/index.css b/app/components/rfd/index.css deleted file mode 100644 index 907797b..0000000 --- a/app/components/rfd/index.css +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright Oxide Computer Company - */ - -.dialog { - opacity: 0; - transition-property: opacity, transform; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 100ms; - transform: translate3d(50%, 0px, 0px); -} - -.dialog[data-enter] { - opacity: 1; - transition-duration: 100ms; - transform: translate3d(0%, 0px, 0px); -} - -.dialog[data-leave] { - transition-duration: 50ms; -} - -.spinner { - --radius: 4; - --PI: 3.14159265358979; - --circumference: calc(var(--PI) * var(--radius) * 2px); - animation: rotate 5s linear infinite; -} - -.spinner .path { - stroke-dasharray: var(--circumference); - transform-origin: center; - animation: dash 4s ease-in-out infinite; - stroke: var(--content-accent); -} - -@media (prefers-reduced-motion) { - .spinner { - animation: rotate 6s linear infinite; - } - - .spinner .path { - animation: none; - stroke-dasharray: 20; - stroke-dashoffset: 100; - } - - .spinner-lg .path { - stroke-dasharray: 50; - } -} - -.spinner .bg { - stroke: var(--content-default); -} - -@keyframes rotate { - 100% { - transform: rotate(360deg); - } -} - -@keyframes dash { - from { - stroke-dashoffset: var(--circumference); - } - to { - stroke-dashoffset: calc(var(--circumference) * -1); - } -} diff --git a/app/root.tsx b/app/root.tsx index 401119d..c222d39 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -119,7 +119,6 @@ const Layout = ({ children, theme }: { children: React.ReactNode; theme?: string - {/* Use plausible analytics only on Vercel */} {process.env.NODE_ENV === 'production' && (