diff --git a/ui/package.json b/ui/package.json index 11569645..1a100b52 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tabs": "^1.0.4", "camaro": "^6.2.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", diff --git a/ui/src/app/about/page.tsx b/ui/src/app/about/page.tsx index 48302376..c8d17f53 100644 --- a/ui/src/app/about/page.tsx +++ b/ui/src/app/about/page.tsx @@ -2,7 +2,7 @@ import Image from 'next/image'; const About = () => (
-
+

What is a UH Grouping?

diff --git a/ui/src/app/admin/page.tsx b/ui/src/app/admin/page.tsx new file mode 100644 index 00000000..5ba4c690 --- /dev/null +++ b/ui/src/app/admin/page.tsx @@ -0,0 +1,58 @@ +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; + +const Admin = () => { + return ( +
+
+
+

UH Groupings Administration

+

+ Search for and manage any grouping on behalf of its owner. + Manage the list of UH Groupings administrators. +

+
+ +
+ + + Manage Groupings + + + Manage Admins + + + Manage Person + + +
+ +
+
+ {/* GroupingsTable goes here */} +
+
+
+ +
+
+ {/* AdminTable goes here */} +
+
+
+ +
+
+ {/* PersonTable goes here */} +
+
+
+
+
+
+ ); +} + +export default Admin; diff --git a/ui/src/app/groupings/page.tsx b/ui/src/app/groupings/page.tsx new file mode 100644 index 00000000..0824e8fa --- /dev/null +++ b/ui/src/app/groupings/page.tsx @@ -0,0 +1,22 @@ +const Groupings = () => { + return ( +
+
+
+

Manage My Groupings

+

+ View and manage groupings I own. Manage members, + configure grouping options and sync destinations. +

+
+
+
+ {/* GroupingsTable goes here */} +
+
+
+
+ ); +} + +export default Groupings; diff --git a/ui/src/app/memberships/page.tsx b/ui/src/app/memberships/page.tsx new file mode 100644 index 00000000..09aa557b --- /dev/null +++ b/ui/src/app/memberships/page.tsx @@ -0,0 +1,46 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; + +const Memberships = () => { + return ( +
+
+
+

Manage My Memberships

+

+ View and manage my memberships. Search for new groupings to join as a member. +

+
+ +
+ + + Current Memberships + + + Membership Opportunities + + +
+ +
+
+ {/* MembershipsTable goes here */} +
+
+
+ +
+
+ {/* MembershipsTable goes here */} +
+
+
+
+
+
+ ); +} + +export default Memberships; diff --git a/ui/src/components/ui/sheet.tsx b/ui/src/components/ui/sheet.tsx index 3b2131bc..da7b8731 100644 --- a/ui/src/components/ui/sheet.tsx +++ b/ui/src/components/ui/sheet.tsx @@ -1,11 +1,11 @@ -'use client' +'use client'; -import * as React from 'react' -import * as SheetPrimitive from '@radix-ui/react-dialog' -import { cva, type VariantProps } from 'class-variance-authority' -import { X } from 'lucide-react' +import * as React from 'react'; +import * as SheetPrimitive from '@radix-ui/react-dialog'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { X } from 'lucide-react'; -import { cn } from '@/components/ui/utils' +import { cn } from '@/components/ui/utils'; const Sheet = SheetPrimitive.Root; diff --git a/ui/src/components/ui/tabs.tsx b/ui/src/components/ui/tabs.tsx new file mode 100644 index 00000000..341e986e --- /dev/null +++ b/ui/src/components/ui/tabs.tsx @@ -0,0 +1,93 @@ +'use client'; + +import * as React from 'react'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from './utils'; + +const Tabs = TabsPrimitive.Root + +const tabsListVariants = cva( + 'inline-flex', + { + variants: { + variant: { + default: 'items-center justify-center h-10 rounded-md bg-muted p-1 text-muted-foreground', + outline: '', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) +const tabsTriggerVariants = cva( + 'inline-flex items-center justify-center', + { + variants: { + variant: { + default: `whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background + transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring + focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 + data-[state=active]:bg-background data-[state=active]:text-foreground + data-[state=active]:shadow-sm`, + outline: `px-4 py-2 text-base font-medium ring-offset-background transition-all + data-[state=active]:bg-white data-[state=active]:rounded-t data-[state=active]:border-b-5 + data-[state=active]:border-black data-[state=active]:text-foreground text-muted-foreground`, + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) +interface TabsListProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const TabsList = React.forwardRef< + React.ElementRef, + TabsListProps +>(({ className, variant, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +interface TabsTriggerProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const TabsTrigger = React.forwardRef< + React.ElementRef, + TabsTriggerProps +>(({ className, variant, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/ui/tests/app/about/About.test.tsx b/ui/tests/app/about/page.test.tsx similarity index 100% rename from ui/tests/app/about/About.test.tsx rename to ui/tests/app/about/page.test.tsx diff --git a/ui/tests/app/admin/page.test.tsx b/ui/tests/app/admin/page.test.tsx new file mode 100644 index 00000000..f2f4cd68 --- /dev/null +++ b/ui/tests/app/admin/page.test.tsx @@ -0,0 +1,20 @@ +import Admin from '@/app/admin/page'; +import { render, screen } from '@testing-library/react'; + +describe('Admin', () => { + + it('should render the Admin page with the appropriate header and tabs', () => { + render(); + expect(screen.getByRole('main')).toBeInTheDocument(); + + expect(screen.getByRole('heading', { name: 'UH Groupings Administration' })).toBeInTheDocument(); + expect(screen.getByText('Search for and manage any grouping on behalf of its owner. ' + + 'Manage the list of UH Groupings administrators.')).toBeInTheDocument(); + + expect(screen.getByRole('tablist')).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Manage Groupings' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Manage Admins' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Manage Person' })).toBeInTheDocument(); + }); + +}); diff --git a/ui/tests/app/groupings/page.test.tsx b/ui/tests/app/groupings/page.test.tsx new file mode 100644 index 00000000..8562eda6 --- /dev/null +++ b/ui/tests/app/groupings/page.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react'; +import Groupings from '@/app/groupings/page'; + +describe('Groupings', () => { + + it('should render the Groupings page with the appropriate header', () => { + render(); + expect(screen.getByRole('main')).toBeInTheDocument(); + + expect(screen.getByRole('heading', { name: 'Manage My Groupings' })).toBeInTheDocument(); + expect(screen.getByText('View and manage groupings I own. ' + + 'Manage members, configure grouping options and sync destinations.')).toBeInTheDocument(); + }); + +}); diff --git a/ui/tests/app/memberships/page.test.tsx b/ui/tests/app/memberships/page.test.tsx new file mode 100644 index 00000000..54e8d714 --- /dev/null +++ b/ui/tests/app/memberships/page.test.tsx @@ -0,0 +1,19 @@ +import Memberships from '@/app/memberships/page'; +import { render, screen } from '@testing-library/react'; + +describe('Memberships', () => { + + it('should render the Memberhsips page with the appropriate header and tabs', () => { + render(); + expect(screen.getByRole('main')).toBeInTheDocument(); + + expect(screen.getByRole('heading', { name: 'Manage My Memberships' })).toBeInTheDocument(); + expect(screen.getByText('View and manage my memberships. ' + + 'Search for new groupings to join as a member.')).toBeInTheDocument(); + + expect(screen.getByRole('tablist')).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Current Memberships' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Membership Opportunities' })).toBeInTheDocument(); + }); + +});