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/button.tsx b/ui/src/components/ui/button.tsx
index 3c469dd0..60a2e796 100644
--- a/ui/src/components/ui/button.tsx
+++ b/ui/src/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react'
-import { Slot } from '@radix-ui/react-slot'
-import { cva, type VariantProps } from 'class-variance-authority'
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/components/ui/utils'
+import { cn } from '@/components/ui/utils';
const buttonVariants = cva(
`inline-flex items-center justify-center whitespace-nowrap rounded-[0.25rem] text-base font-normal ring-offset-white
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();
+ });
+
+});