From 67f3a3829e2f629e72ec3c32b042be2ef08c38e9 Mon Sep 17 00:00:00 2001 From: Ryan Cohen Date: Wed, 24 Aug 2022 10:49:25 +0900 Subject: [PATCH] feat: improved user dropdown (#2969) --- src/assets/infinity.svg | 1 + .../UserDropdown/MiniQuotaDisplay/index.tsx | 93 +++++++++ src/components/Layout/UserDropdown/index.tsx | 184 +++++++++++------- src/i18n/locale/en.json | 3 + tailwind.config.js | 1 + 5 files changed, 212 insertions(+), 70 deletions(-) create mode 100644 src/assets/infinity.svg create mode 100644 src/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsx diff --git a/src/assets/infinity.svg b/src/assets/infinity.svg new file mode 100644 index 00000000..054149f8 --- /dev/null +++ b/src/assets/infinity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsx b/src/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsx new file mode 100644 index 00000000..abc08dd1 --- /dev/null +++ b/src/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsx @@ -0,0 +1,93 @@ +import Infinity from '@app/assets/infinity.svg'; +import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner'; +import ProgressCircle from '@app/components/Common/ProgressCircle'; +import type { QuotaResponse } from '@server/interfaces/api/userInterfaces'; +import { defineMessages, useIntl } from 'react-intl'; +import useSWR from 'swr'; + +const messages = defineMessages({ + movierequests: 'Movie Requests', + seriesrequests: 'Series Requests', +}); + +type MiniQuotaDisplayProps = { + userId: number; +}; + +const MiniQuotaDisplay = ({ userId }: MiniQuotaDisplayProps) => { + const intl = useIntl(); + const { data, error } = useSWR(`/api/v1/user/${userId}/quota`); + + if (error) { + return null; + } + + if (!data && !error) { + return ; + } + + return ( + <> + {((data?.movie.limit ?? 0) !== 0 || (data?.tv.limit ?? 0) !== 0) && ( +
+
+
+ {intl.formatMessage(messages.movierequests)} +
+
+ {data?.movie.limit ?? 0 > 0 ? ( + <> + + + {data?.movie.remaining} / {data?.movie.limit} + + + ) : ( + <> + + Unlimited + + )} +
+
+
+
+ {intl.formatMessage(messages.seriesrequests)} +
+
+ {data?.tv.limit ?? 0 > 0 ? ( + <> + + + {data?.tv.remaining} / {data?.tv.limit} + + + ) : ( + <> + + Unlimited + + )} +
+
+
+ )} + + ); +}; + +export default MiniQuotaDisplay; diff --git a/src/components/Layout/UserDropdown/index.tsx b/src/components/Layout/UserDropdown/index.tsx index 6078459a..57481b71 100644 --- a/src/components/Layout/UserDropdown/index.tsx +++ b/src/components/Layout/UserDropdown/index.tsx @@ -1,25 +1,39 @@ -import Transition from '@app/components/Transition'; -import useClickOutside from '@app/hooks/useClickOutside'; +import MiniQuotaDisplay from '@app/components/Layout/UserDropdown/MiniQuotaDisplay'; import { useUser } from '@app/hooks/useUser'; -import { LogoutIcon } from '@heroicons/react/outline'; +import { Menu, Transition } from '@headlessui/react'; +import { ClockIcon, LogoutIcon } from '@heroicons/react/outline'; import { CogIcon, UserIcon } from '@heroicons/react/solid'; import axios from 'axios'; +import type { LinkProps } from 'next/link'; import Link from 'next/link'; -import { useRef, useState } from 'react'; +import { forwardRef, Fragment } from 'react'; import { defineMessages, useIntl } from 'react-intl'; const messages = defineMessages({ myprofile: 'Profile', settings: 'Settings', + requests: 'Requests', signout: 'Sign Out', }); +const ForwardedLink = forwardRef< + HTMLAnchorElement, + LinkProps & React.ComponentPropsWithoutRef<'a'> +>(({ href, children, ...rest }, ref) => { + return ( + + + {children} + + + ); +}); + +ForwardedLink.displayName = 'ForwardedLink'; + const UserDropdown = () => { const intl = useIntl(); - const dropdownRef = useRef(null); const { user, revalidate } = useUser(); - const [isDropdownOpen, setDropdownOpen] = useState(false); - useClickOutside(dropdownRef, () => setDropdownOpen(false)); const logout = async () => { const response = await axios.post('/api/v1/auth/logout'); @@ -30,14 +44,10 @@ const UserDropdown = () => { }; return ( -
+
- +
-
-
- - { - if (e.key === 'Enter') { - setDropdownOpen(false); - } - }} - onClick={() => setDropdownOpen(false)} - data-testid="user-menu-profile" - > - - {intl.formatMessage(messages.myprofile)} - - - - { - if (e.key === 'Enter') { - setDropdownOpen(false); - } - }} - onClick={() => setDropdownOpen(false)} - data-testid="user-menu-settings" - > - - {intl.formatMessage(messages.settings)} - - - logout()} - > - - {intl.formatMessage(messages.signout)} - + +
+
+
+ +
+ + {user?.displayName} + + + {user?.email} + +
+
+ {user && } +
+
+ + {({ active }) => ( + + + {intl.formatMessage(messages.myprofile)} + + )} + + + {({ active }) => ( + + + {intl.formatMessage(messages.requests)} + + )} + + + {({ active }) => ( + + + {intl.formatMessage(messages.settings)} + + )} + + + {({ active }) => ( + logout()} + > + + {intl.formatMessage(messages.signout)} + + )} + +
-
+ -
+
); }; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 310894b2..1cc403ce 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -114,7 +114,10 @@ "components.Layout.Sidebar.requests": "Requests", "components.Layout.Sidebar.settings": "Settings", "components.Layout.Sidebar.users": "Users", + "components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Movie Requests", + "components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Series Requests", "components.Layout.UserDropdown.myprofile": "Profile", + "components.Layout.UserDropdown.requests": "Requests", "components.Layout.UserDropdown.settings": "Settings", "components.Layout.UserDropdown.signout": "Sign Out", "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind", diff --git a/tailwind.config.js b/tailwind.config.js index d1d023c0..96b5faad 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const defaultTheme = require('tailwindcss/defaultTheme'); +/** @type {import('tailwindcss').Config} */ module.exports = { mode: 'jit', content: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'],