Skip to content

Commit

Permalink
Refactor modal and use it for safety rating
Browse files Browse the repository at this point in the history
  • Loading branch information
razzeee committed Aug 20, 2023
1 parent 4145e97 commit b9e407a
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 139 deletions.
1 change: 1 addition & 0 deletions frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"close": "Close",
"go-home": "Go Home",
"search-apps": "Search apps",
"publish": "Publish",
Expand Down
149 changes: 102 additions & 47 deletions frontend/src/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,130 @@ import { Dialog, Transition } from "@headlessui/react"
import { Fragment, FunctionComponent } from "react"
import Button from "./Button"
import { useTranslation } from "react-i18next"
import { HiXMark } from "react-icons/hi2"
import clsx from "clsx"

interface Props {
shown: boolean
submitButtonText: string
isSubmitButtonDisabled?: boolean
onSubmit: () => void
cancelButtonText?: string
onCancel: () => void
title: string
description: string
centerTitle?: boolean
description?: string
onClose: () => void
children?: React.ReactNode
cancelButton?: {
label?: string
onClick: () => void
disabled?: boolean
}
submitButton?: {
label?: string
onClick: () => void
disabled?: boolean
}
}

const Modal: FunctionComponent<Props> = ({
shown,
submitButtonText,
isSubmitButtonDisabled,
onSubmit,
cancelButtonText = "cancel",
onCancel,
title,
centerTitle,
description,
onClose,
children,
cancelButton,
submitButton,
}) => {
const { t } = useTranslation()

return (
<>
<Transition appear show={shown} as={Fragment}>
<Dialog
as="div"
onClose={() => {
onCancel()
}}
>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<Transition.Root show={shown} as={Fragment}>
<Dialog as="div" className="relative z-40" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
aria-hidden
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
/>
</Transition.Child>

<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="inline-flex flex-col justify-center space-y-6 rounded-xl bg-flathub-gainsborow p-14 shadow-md dark:bg-flathub-dark-gunmetal">
<Dialog.Title className="m-0">{title}</Dialog.Title>
<Dialog.Description className="m-0">
{description}
</Dialog.Description>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-xl bg-flathub-white px-4 pb-4 pt-5 text-left shadow-xl transition-all dark:bg-flathub-dark-gunmetal sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute end-0 top-0 hidden pe-6 pt-6 sm:block">
<button
type="button"
className="rounded-md bg-flathub-white text-flathub-spanish-gray hover:text-flathub-gray-x11 focus:outline-none focus:ring-2 focus:ring-flathub-celestial-blue
focus:ring-offset-2 dark:bg-flathub-dark-gunmetal dark:text-flathub-gainsborow hover:dark:text-flathub-spanish-gray"
onClick={() => onClose()}
>
<span className="sr-only">{t("close")}</span>
<HiXMark className="h-6 w-6" aria-hidden="true" />
</button>
</div>

{children}
<Dialog.Title
as="h3"
className={clsx(
centerTitle && "text-center",
"text-lg font-semibold pb-5",
)}
>
{title}
</Dialog.Title>
{description && (
<Dialog.Description className="m-0 pb-4">
{description}
</Dialog.Description>
)}

<div className="mt-3 grid grid-cols-2 gap-6">
<Button
className="col-start-1"
onClick={onCancel}
variant="secondary"
aria-label={t("cancel")}
>
{t(cancelButtonText)}
</Button>
<Button
className="col-start-2"
onClick={onSubmit}
variant="primary"
aria-label={submitButtonText}
disabled={isSubmitButtonDisabled}
>
{t(submitButtonText)}
</Button>
</div>
</Dialog.Panel>
{children}

<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
{submitButton && (
<Button
className="inline-flex w-full justify-center px-3 py-2 sm:ms-3 sm:w-auto"
onClick={submitButton.onClick}
variant="primary"
aria-label={submitButton.label ?? t("submit")}
disabled={submitButton.disabled}
>
{t(submitButton.label ?? t("submit"))}
</Button>
)}
{cancelButton && (
<Button
className="mt-3 inline-flex w-full justify-center px-3 py-2 sm:mt-0 sm:w-auto"
onClick={cancelButton.onClick}
variant="secondary"
aria-label={cancelButton.label ?? t("cancel")}
disabled={cancelButton.disabled}
>
{t(cancelButton.label ?? t("cancel"))}
</Button>
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</Transition.Root>
</>
)
}
Expand Down
44 changes: 27 additions & 17 deletions frontend/src/components/application/AppDevelopersControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Button from "../Button"
import InlineError from "../InlineError"
import Spinner from "../Spinner"
import ConfirmDialog from "../ConfirmDialog"
import { Dialog, Transition } from "@headlessui/react"
import { useUserDispatch } from "src/context/user-info"
import { useRouter } from "next/router"
import { useQuery } from "@tanstack/react-query"
Expand Down Expand Up @@ -235,32 +234,43 @@ const InviteDialog: FunctionComponent<InviteDialogProps> = ({

const [error, setError] = useState<string | null>(null)

const resetModal = () => {
setInviteCode("")
setError(null)
}

return (
<Modal
shown={isVisible}
submitButtonText={t("invite")}
onSubmit={async () => {
try {
await inviteDeveloper(app.id, inviteCode)
} catch (e) {
setError(e.replaceAll("_", "-"))
return
}
refresh()
onClose={() => {
closeDialog()
setInviteCode("")
setError(null)
resetModal()
}}
onCancel={() => {
closeDialog()
setInviteCode("")
setError(null)
submitButton={{
label: t("invite"),
onClick: async () => {
try {
await inviteDeveloper(app.id, inviteCode)
} catch (e) {
setError(e.replaceAll("_", "-"))
return
}
refresh()
closeDialog()
resetModal()
},
disabled: inviteCode.length === 0,
}}
cancelButton={{
onClick: () => {
closeDialog()
resetModal()
},
}}
title={t("invite-developer")}
description={t("invite-developer-description", {
developersTab: t("developers"),
})}
isSubmitButtonDisabled={inviteCode.length === 0}
>
<InlineError error={t(error)} shown={!!error} />
<input
Expand Down
108 changes: 33 additions & 75 deletions frontend/src/components/application/SafetyRating.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Dialog, Transition } from "@headlessui/react"
import clsx from "clsx"
import { useTranslation } from "next-i18next"
import { Fragment, FunctionComponent, createElement, useState } from "react"
import { HiXMark } from "react-icons/hi2"
import { FunctionComponent, createElement, useState } from "react"
import {
getSafetyRating,
safetyRatingToColor,
Expand All @@ -13,6 +11,7 @@ import { Appstream } from "src/types/Appstream"
import { Summary } from "src/types/Summary"
import { StackedListBox } from "./StackedListBox"
import { IconType } from "react-icons"
import Modal from "../Modal"

interface Props {
data: Appstream
Expand Down Expand Up @@ -77,81 +76,40 @@ const SafetyRating: FunctionComponent<Props> = ({ data, summary }) => {
</div>
</button>

<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-40" onClose={setIsOpen}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-flathub-white px-4 pb-4 pt-5 text-left shadow-xl transition-all dark:bg-flathub-dark-gunmetal sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-flathub-white text-flathub-spanish-gray hover:text-flathub-gray-x11 focus:outline-none focus:ring-2 focus:ring-flathub-celestial-blue
focus:ring-offset-2 dark:bg-flathub-dark-gunmetal dark:text-flathub-gainsborow hover:dark:text-flathub-spanish-gray"
onClick={() => setIsOpen(false)}
>
<span className="sr-only">Close</span>
<HiXMark className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="flex flex-col items-center gap-2">
<Modal
shown={isOpen}
centerTitle
onClose={() => setIsOpen(false)}
title={t(`appname-is-safety-rating-${highestSafetyRating}`, {
appName: data.name,
})}
>
<>
<div className="flex flex-col items-center gap-2">
<SafetyRatingIcon
highestSafetyRating={highestSafetyRating}
size="large"
/>
</div>
<div className="mt-2 w-full">
<StackedListBox
items={safetyRating
.sort((a, b) => b.safetyRating - a.safetyRating)
.map(({ description, safetyRating, icon }, i) => ({
id: i,
header: t(description),
icon: (
<SafetyRatingIcon
highestSafetyRating={highestSafetyRating}
size="large"
highestSafetyRating={safetyRating}
size="small"
icon={icon}
/>
<Dialog.Title
as="h3"
className="text-center text-base font-semibold leading-6"
>
{t(`appname-is-safety-rating-${highestSafetyRating}`, {
appName: data.name,
})}
</Dialog.Title>
<div className="mt-2 w-full">
<StackedListBox
items={safetyRating
.sort((a, b) => b.safetyRating - a.safetyRating)
.map(({ description, safetyRating, icon }, i) => ({
id: i,
header: t(description),
icon: (
<SafetyRatingIcon
highestSafetyRating={safetyRating}
size="small"
icon={icon}
/>
),
}))}
/>
<ul></ul>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
),
}))}
/>
</div>
</Dialog>
</Transition.Root>
</>
</Modal>
</>
)
}
Expand Down

0 comments on commit b9e407a

Please sign in to comment.