From 2b488abf78c967328ecf84d54600e23648c4913d Mon Sep 17 00:00:00 2001 From: Anna Shaforostova <110382186+gigsanna@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:13:18 +0200 Subject: [PATCH] Add fake login form (#3) remove GIGS_MANAGABLE_USER_EMAIL in favour of email login form; add fake login form storing email in a cookie; add auth protection: only allow going to /checkout and /backoffice if logged in --- .env.example | 1 - README.md | 5 +- app/(manage)/backoffice/layout.tsx | 13 + app/(purchase)/checkout/layout.tsx | 13 + app/(purchase)/checkout/page.tsx | 10 +- app/setup-error/page.tsx | 11 +- components/LoginForm.tsx | 28 ++ components/SideNav.tsx | 6 +- components/SideNavLogoutButton.tsx | 16 ++ components/UserButton.tsx | 43 +++ components/ui/dropdown.tsx | 57 ++++ components/ui/input.tsx | 26 ++ lib/actions.ts | 13 +- lib/api.ts | 4 +- lib/applicationMocks.ts | 11 +- lib/utils.ts | 3 +- package-lock.json | 438 +++++++++++++++++++++++++++++ package.json | 1 + 18 files changed, 673 insertions(+), 26 deletions(-) create mode 100644 app/(manage)/backoffice/layout.tsx create mode 100644 app/(purchase)/checkout/layout.tsx create mode 100644 components/LoginForm.tsx create mode 100644 components/SideNavLogoutButton.tsx create mode 100644 components/UserButton.tsx create mode 100644 components/ui/dropdown.tsx create mode 100644 components/ui/input.tsx diff --git a/.env.example b/.env.example index 61190b4..285595d 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,2 @@ GIGS_PROJECT= GIGS_API_KEY= -GIGS_MANAGABLE_USER_EMAIL= diff --git a/README.md b/README.md index 0148d7d..8360460 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Connect Sessions Buy & Manage example -This application showcase how Connect Sessions can be used to buy and manag phone plans through Connect from your existing application. This is a developer reference. +This application showcase how Connect Sessions can be used to buy and manage phone plans through Connect from your existing application. This is a developer reference. To get the most out of this, please read the [associated guide](https://developers.gigs.com/docs/api/805ba2c145553-example-purchasing-and-managing-subscriptions-using-connect-sessions). @@ -20,7 +20,7 @@ In order to run this example locally, you need: - an existing Project with Connect enabled and at least one plan and one add-on configured - a valid API key -To setup the example: +To set up the example: **Clone the repository** @@ -45,7 +45,6 @@ Set the required environment variables: - `GIGS_PROJECT`: The name of your project (the yourproject part from your yourproject.gigs.com Connect url) - `GIGS_API_KEY`: Your API key -- `GIGS_MANAGABLE_USER_EMAIL`: The desired email of your user (it does not have to exist in your project yet, but you **should have access to the emails**) **Start the app** diff --git a/app/(manage)/backoffice/layout.tsx b/app/(manage)/backoffice/layout.tsx new file mode 100644 index 0000000..dfdcc61 --- /dev/null +++ b/app/(manage)/backoffice/layout.tsx @@ -0,0 +1,13 @@ +import { auth } from '@/lib/applicationMocks' +import { LoginForm } from '@/components/LoginForm' + +type Props = { + children: React.ReactNode +} + +const Layout = async ({ children }: Props) => { + const currentUser = auth.getUser() + return currentUser.email ? <>{children} : +} + +export default Layout diff --git a/app/(purchase)/checkout/layout.tsx b/app/(purchase)/checkout/layout.tsx new file mode 100644 index 0000000..dfdcc61 --- /dev/null +++ b/app/(purchase)/checkout/layout.tsx @@ -0,0 +1,13 @@ +import { auth } from '@/lib/applicationMocks' +import { LoginForm } from '@/components/LoginForm' + +type Props = { + children: React.ReactNode +} + +const Layout = async ({ children }: Props) => { + const currentUser = auth.getUser() + return currentUser.email ? <>{children} : +} + +export default Layout diff --git a/app/(purchase)/checkout/page.tsx b/app/(purchase)/checkout/page.tsx index f301dac..8e137d0 100644 --- a/app/(purchase)/checkout/page.tsx +++ b/app/(purchase)/checkout/page.tsx @@ -10,9 +10,10 @@ import { } from '@/components/ui/carousel' import { getPlans } from '@/lib/api' import { envVarsPresent } from '@/lib/utils' -import { UserCircle } from 'lucide-react' import Image from 'next/image' import { redirect } from 'next/navigation' +import { UserButton } from '@/components/UserButton' +import { auth } from '@/lib/applicationMocks' const CheckoutPage = async () => { if (!envVarsPresent()) { @@ -32,12 +33,7 @@ const CheckoutPage = async () => { src="/tigr-logo.webp" width={100} /> -
-
- - {process.env.GIGS_MANAGABLE_USER_EMAIL} -
-
+

diff --git a/app/setup-error/page.tsx b/app/setup-error/page.tsx index 81e730c..a8b5bc8 100644 --- a/app/setup-error/page.tsx +++ b/app/setup-error/page.tsx @@ -4,7 +4,6 @@ import Link from 'next/link' const SetupErrorPage = () => { const gigsProject = process.env.GIGS_PROJECT const gigsAPIKey = process.env.GIGS_API_KEY - const gigsUser = process.env.GIGS_MANAGABLE_USER_EMAIL return (
@@ -25,17 +24,13 @@ const SetupErrorPage = () => {
- + GIGS_PROJECT
- + GIGS_API_KEY
-
- - GIGS_MANAGABLE_USER_EMAIL -
{ ) } -const PresentMissinIcon = ({ present }: { present: boolean }) => { +const PresentMissingIcon = ({ present }: { present: boolean }) => { if (present) { return } diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx new file mode 100644 index 0000000..ab3b865 --- /dev/null +++ b/components/LoginForm.tsx @@ -0,0 +1,28 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { login } from '@/lib/actions' +import { Input } from '@/components/ui/input' + +export const LoginForm = () => { + return ( +
+
+
+ + +
+ +
+
+ ) +} diff --git a/components/SideNav.tsx b/components/SideNav.tsx index f61c37a..159f992 100644 --- a/components/SideNav.tsx +++ b/components/SideNav.tsx @@ -2,6 +2,8 @@ import Link from 'next/link' import Image from 'next/image' import { Phone, ShoppingCart, Smartphone, User2 } from 'lucide-react' +import { SideNavLogoutButton } from '@/components/SideNavLogoutButton' + export const SideNav = () => { return (
@@ -15,7 +17,7 @@ export const SideNav = () => { />
-
diff --git a/components/SideNavLogoutButton.tsx b/components/SideNavLogoutButton.tsx new file mode 100644 index 0000000..5eb1c81 --- /dev/null +++ b/components/SideNavLogoutButton.tsx @@ -0,0 +1,16 @@ +'use client' + +import { logout } from '@/lib/actions' +import { LogOut } from 'lucide-react' + +export const SideNavLogoutButton = () => { + return ( + + ) +} diff --git a/components/UserButton.tsx b/components/UserButton.tsx new file mode 100644 index 0000000..a8f1217 --- /dev/null +++ b/components/UserButton.tsx @@ -0,0 +1,43 @@ +'use client' + +import { logout } from '@/lib/actions' +import { UserCircle, LogOut } from 'lucide-react' + +import { + Dropdown, + DropdownContent, + DropdownItem, + DropdownPortal, + DropdownTrigger, +} from '@/components/ui/dropdown' + +type UserButtonProps = { + user: string | undefined +} + +export const UserButton = ({ user }: UserButtonProps) => { + if (!user) { + return null + } + + return ( + + +
+ + {user} +
+
+ + + logout()} className="text-end"> +
+ + Log out +
+
+
+
+
+ ) +} diff --git a/components/ui/dropdown.tsx b/components/ui/dropdown.tsx new file mode 100644 index 0000000..0a4e0c7 --- /dev/null +++ b/components/ui/dropdown.tsx @@ -0,0 +1,57 @@ +'use client' + +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' + +import { cn } from '@/lib/utils' + +const Dropdown = DropdownMenuPrimitive.Root + +const DropdownTrigger = DropdownMenuPrimitive.Trigger + +const DropdownPortal = DropdownMenuPrimitive.Portal + +const DropdownContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + +)) +DropdownContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + +)) +DropdownItem.displayName = DropdownMenuPrimitive.Item.displayName + +export { + Dropdown, + DropdownTrigger, + DropdownContent, + DropdownPortal, + DropdownItem, +} diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..93db607 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +export const Input = React.forwardRef< + HTMLInputElement, + React.InputHTMLAttributes +>((props, ref) => { + const { className, ...inputProps } = props + + return ( + + ) +}) diff --git a/lib/actions.ts b/lib/actions.ts index 213caf8..89f869f 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -1,7 +1,7 @@ 'use server' import { createConnectSession, findUser } from './api' -import { auth } from './applicationMocks' +import { auth, resetUserEmail, setUserEmail } from './applicationMocks' import { ConnectSessionParams } from './schemas/connectSession' export const checkoutCurrentUserWithPlan = async (planId: string) => { @@ -111,3 +111,14 @@ export const checkoutAddon = async ( return await createConnectSession(connectSession) } + +export const login = (formData: FormData) => { + const email = formData.get('email') as string + + // perform an actual authentication call here + setUserEmail(email) +} + +export const logout = () => { + resetUserEmail() +} diff --git a/lib/api.ts b/lib/api.ts index 532fd2e..47d8a26 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -23,7 +23,7 @@ type ApiCollectionResponse = { data?: T[] } -type ApiItemResonse = { +type ApiItemResponse = { error?: string data?: T } @@ -119,7 +119,7 @@ export const getAddons = async ( export const createConnectSession = async ( connectSession: ConnectSessionParams, -): Promise> => { +): Promise> => { const options: RequestInit = { method: 'POST', headers, diff --git a/lib/applicationMocks.ts b/lib/applicationMocks.ts index c5ebdeb..0288af7 100644 --- a/lib/applicationMocks.ts +++ b/lib/applicationMocks.ts @@ -1,6 +1,15 @@ +import { cookies } from 'next/headers' + +const loginCookie = 'connect_session_example_user' + +export const setUserEmail = (email: string) => + cookies().set(loginCookie, email, { maxAge: 3600 * 24, sameSite: 'strict' }) +export const getUserEmail = () => cookies().get(loginCookie)?.value ?? '' +export const resetUserEmail = () => cookies().delete(loginCookie) + export const auth = { getUser: () => ({ - email: process.env.GIGS_MANAGABLE_USER_EMAIL!, + email: getUserEmail(), birthday: '1991-07-22', fullName: 'John Doe', }), diff --git a/lib/utils.ts b/lib/utils.ts index 1754bd3..6c36a9d 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -13,7 +13,6 @@ export type ElementOf = T extends readonly (infer E)[] ? E : never export const envVarsPresent = () => { const gigsProject = process.env.GIGS_PROJECT const gigsAPIKey = process.env.GIGS_API_KEY - const gigsUser = process.env.GIGS_MANAGABLE_USER_EMAIL - return !!gigsProject && !!gigsAPIKey && !!gigsUser + return !!gigsProject && !!gigsAPIKey } diff --git a/package-lock.json b/package-lock.json index c57431e..fa2480d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@heroicons/react": "^2.1.1", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -121,6 +122,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@heroicons/react": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz", @@ -441,6 +476,55 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -511,6 +595,23 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", @@ -538,6 +639,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -598,6 +728,78 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", @@ -692,6 +894,37 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -780,6 +1013,50 @@ } } }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", @@ -5624,6 +5901,36 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==" }, + "@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "requires": { + "@floating-ui/utils": "^0.2.1" + } + }, + "@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "requires": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "requires": { + "@floating-ui/dom": "^1.6.1" + } + }, + "@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "@heroicons/react": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz", @@ -5817,6 +6124,27 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + } + }, + "@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + } + }, "@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -5855,6 +6183,14 @@ "react-remove-scroll": "2.5.5" } }, + "@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@radix-ui/react-dismissable-layer": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", @@ -5868,6 +6204,21 @@ "@radix-ui/react-use-escape-keydown": "1.0.3" } }, + "@radix-ui/react-dropdown-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + } + }, "@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -5896,6 +6247,50 @@ "@radix-ui/react-use-layout-effect": "1.0.1" } }, + "@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + } + }, + "@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "requires": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + } + }, "@radix-ui/react-portal": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", @@ -5934,6 +6329,23 @@ "@radix-ui/react-primitive": "1.0.3" } }, + "@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + } + }, "@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -5977,6 +6389,32 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + } + }, + "@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + } + }, + "@radix-ui/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@rushstack/eslint-patch": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", diff --git a/package.json b/package.json index 7c30423..2ee2c21 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@heroicons/react": "^2.1.1", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@typescript-eslint/eslint-plugin": "^6.21.0",