Compare commits
2 Commits
da366272b4
...
features
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0ee505339 | ||
|
|
21338f1541 |
17
src/App.js
17
src/App.js
@@ -239,6 +239,20 @@ function App() {
|
|||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
} />
|
} />
|
||||||
|
<Route path="/admin/registration" element={
|
||||||
|
<PrivateRoute adminOnly>
|
||||||
|
<AdminLayout>
|
||||||
|
<AdminRegistrationBuilder />
|
||||||
|
</AdminLayout>
|
||||||
|
</PrivateRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/admin/member-tiers" element={
|
||||||
|
<PrivateRoute adminOnly>
|
||||||
|
<AdminLayout>
|
||||||
|
<AdminMemberTiers />
|
||||||
|
</AdminLayout>
|
||||||
|
</PrivateRoute>
|
||||||
|
} />
|
||||||
<Route path="/admin/plans" element={
|
<Route path="/admin/plans" element={
|
||||||
<PrivateRoute adminOnly>
|
<PrivateRoute adminOnly>
|
||||||
<AdminLayout>
|
<AdminLayout>
|
||||||
@@ -293,6 +307,7 @@ function App() {
|
|||||||
<Navigate to="/admin/settings/permissions" replace />
|
<Navigate to="/admin/settings/permissions" replace />
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
} />
|
} />
|
||||||
|
|
||||||
<Route path="/admin/settings" element={
|
<Route path="/admin/settings" element={
|
||||||
<PrivateRoute adminOnly>
|
<PrivateRoute adminOnly>
|
||||||
<AdminLayout>
|
<AdminLayout>
|
||||||
@@ -303,9 +318,7 @@ function App() {
|
|||||||
<Route index element={<Navigate to="stripe" replace />} />
|
<Route index element={<Navigate to="stripe" replace />} />
|
||||||
<Route path="stripe" element={<AdminSettings />} />
|
<Route path="stripe" element={<AdminSettings />} />
|
||||||
<Route path="permissions" element={<AdminRoles />} />
|
<Route path="permissions" element={<AdminRoles />} />
|
||||||
<Route path="member-tiers" element={<AdminMemberTiers />} />
|
|
||||||
<Route path="theme" element={<AdminTheme />} />
|
<Route path="theme" element={<AdminTheme />} />
|
||||||
<Route path="registration" element={<AdminRegistrationBuilder />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* 404 - Catch all undefined routes */}
|
{/* 404 - Catch all undefined routes */}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import {
|
|||||||
Heart,
|
Heart,
|
||||||
Sun,
|
Sun,
|
||||||
Moon,
|
Moon,
|
||||||
|
Star,
|
||||||
|
FileEdit
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||||
@@ -104,18 +106,31 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
path: '/admin',
|
path: '/admin',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'Staff',
|
name: 'Staff & Admins',
|
||||||
icon: UserCog,
|
icon: UserCog,
|
||||||
path: '/admin/staff',
|
path: '/admin/staff',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Members',
|
name: 'Member Roster',
|
||||||
icon: Users,
|
icon: Users,
|
||||||
path: '/admin/members',
|
path: '/admin/members',
|
||||||
disabled: false
|
disabled: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Member Tiers',
|
||||||
|
icon: Star,
|
||||||
|
path: '/admin/member-tiers',
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Registration',
|
||||||
|
icon: FileEdit,
|
||||||
|
path: '/admin/registration',
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Validations',
|
name: 'Validations',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
@@ -316,6 +331,18 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
{/* Dashboard - Standalone */}
|
{/* Dashboard - Standalone */}
|
||||||
{renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))}
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Dashboard'))}
|
||||||
|
|
||||||
|
{/* Onboarding Section */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="px-4 py-2 mt-6">
|
||||||
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
|
Onboarding
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="space-y-1">
|
||||||
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Registration'))}
|
||||||
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Validations'))}
|
||||||
|
</div>
|
||||||
{/* MEMBERSHIP Section */}
|
{/* MEMBERSHIP Section */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="px-4 py-2 mt-6">
|
<div className="px-4 py-2 mt-6">
|
||||||
@@ -325,9 +352,9 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{renderNavItem(filteredNavItems.find(item => item.name === 'Staff'))}
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Member Roster'))}
|
||||||
{renderNavItem(filteredNavItems.find(item => item.name === 'Members'))}
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Member Tiers'))}
|
||||||
{renderNavItem(filteredNavItems.find(item => item.name === 'Validations'))}
|
{renderNavItem(filteredNavItems.find(item => item.name === 'Staff & Admins'))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* FINANCIALS Section */}
|
{/* FINANCIALS Section */}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { CreditCard, Shield, Star, Palette, FileEdit } from 'lucide-react';
|
|||||||
const settingsItems = [
|
const settingsItems = [
|
||||||
{ label: 'Stripe', path: '/admin/settings/stripe', icon: CreditCard },
|
{ label: 'Stripe', path: '/admin/settings/stripe', icon: CreditCard },
|
||||||
{ label: 'Permissions', path: '/admin/settings/permissions', icon: Shield },
|
{ label: 'Permissions', path: '/admin/settings/permissions', icon: Shield },
|
||||||
{ label: 'Member Tiers', path: '/admin/settings/member-tiers', icon: Star },
|
|
||||||
{ label: 'Theme', path: '/admin/settings/theme', icon: Palette },
|
{ label: 'Theme', path: '/admin/settings/theme', icon: Palette },
|
||||||
{ label: 'Registration Form', path: '/admin/settings/registration', icon: FileEdit },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const SettingsTabs = () => {
|
const SettingsTabs = () => {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const AdminDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Link to={'/'} className=''>
|
<Link to={'/'} className=''>
|
||||||
<Button
|
<Button
|
||||||
className="btn-lavender mb-8 md:mb-0 "
|
className="btn-lavender mb-8 md:mb-0 mr-4 "
|
||||||
>
|
>
|
||||||
<Globe />
|
<Globe />
|
||||||
View Public Site
|
View Public Site
|
||||||
|
|||||||
@@ -150,9 +150,15 @@ const AdminMemberTiers = () => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header and Actions */}
|
{/* Header and Actions */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl md:text-5xl font-semibold text-[var(--purple-ink)] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||||
|
Members Tiers
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Configure tier names, time ranges, and badges displayed in the members directory.
|
Configure tier names, time ranges, and badges displayed in the members directory.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<Button variant="outline" onClick={handleDiscardChanges}>
|
<Button variant="outline" onClick={handleDiscardChanges}>
|
||||||
|
|||||||
@@ -498,12 +498,13 @@ const AdminRegistrationBuilder = () => {
|
|||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-lg font-semibold">Steps</h2>
|
<h2 className="text-lg font-semibold">Steps</h2>
|
||||||
<Button size="sm" variant="ghost" onClick={() => setAddStepDialogOpen(true)}>
|
<Button size="sm" variant="ghost" className='w-32' onClick={() => setAddStepDialogOpen(true)}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
|
<p>Add Step</p>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-y-2">
|
<div className="flex">
|
||||||
{sortedSteps.map((step, index) => (
|
{sortedSteps.map((step, index) => (
|
||||||
<div
|
<div
|
||||||
key={step.id}
|
key={step.id}
|
||||||
@@ -519,8 +520,7 @@ const AdminRegistrationBuilder = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="font-medium">Step: </div>
|
<div className="font-medium">Step: {index + 1} </div>
|
||||||
<span className="font-medium text-sm">{step.title}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Mod Buttons */}
|
{/* Mod Buttons */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -548,10 +548,11 @@ const AdminRegistrationBuilder = () => {
|
|||||||
>
|
>
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button> */}
|
</Button> */}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-6 w-6 text-red-500 hover:text-white ml-2"
|
className="h-6 w-6 text-red-500 hover-text-background ml-2"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDeleteStep(step.id);
|
handleDeleteStep(step.id);
|
||||||
@@ -568,91 +569,66 @@ const AdminRegistrationBuilder = () => {
|
|||||||
{/* Sections for selected step */}
|
{/* Sections for selected step */}
|
||||||
{currentStep && (
|
{currentStep && (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between items-center mt-6 mb-4">
|
<div className="flex justify-between flex-col items-center mt-6 mb-4">
|
||||||
<h2 className="text-lg font-semibold">Sections</h2>
|
<h2 className="text-lg font-semibold self-start">Sections</h2>
|
||||||
<Button size="sm" variant="ghost" onClick={() => setAddSectionDialogOpen(true)}>
|
<Button size="sm" className='w-full' variant="ghost" onClick={() => setAddSectionDialogOpen(true)}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
|
<p>Add Section</p>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 ">
|
<div className="space-y-2 ">
|
||||||
{sortedSections.map((section) => (
|
{sortedSections.map((section) => {
|
||||||
<div
|
const sortedFields = section.fields?.sort((a, b) => a.order - b.order) || [];
|
||||||
|
|
||||||
|
return (<div
|
||||||
key={section.id}
|
key={section.id}
|
||||||
className={`p-3 rounded-lg border cursor-pointer transition-colors ${selectedSection === section.id
|
className='p-3 rounded-lg bg-background'
|
||||||
? 'border-brand-purple bg-brand-lavender/10'
|
|
||||||
: 'border-gray-200 hover:border-gray-300'
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedSection(section.id);
|
setSelectedSection(section.id);
|
||||||
setSelectedField(null);
|
setSelectedField(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between ">
|
<div className="flex items-center justify-between ">
|
||||||
<span className="text-sm">{section.title}</span>
|
<div className='flex flex-col'>
|
||||||
|
<span className="text-xl font-semibold">{section.title}</span>
|
||||||
|
{section.description && (
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">{section.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-6 w-6 text-red-500 hover:text-red-700"
|
className=" text-red-500 self-start hover-text-background"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDeleteSection(section.id);
|
handleDeleteSection(section.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<p>Delete Section</p>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="p-6 mt-4 border-brand-purple rounded-xl bg-brand-lavender/10 ">
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
|
||||||
|
|
||||||
{/* Center - Form Canvas */}
|
|
||||||
<div className="lg:col-span-9">
|
|
||||||
|
|
||||||
<Card className="p-6">
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mb-6 ">
|
<div className="flex justify-between items-center mb-6 ">
|
||||||
<div className='relative -mx-11 -my-2 flex gap-2 items-center'>
|
|
||||||
<Grip className="size-10 text-gray-400 py-2 bg-background" />
|
|
||||||
|
|
||||||
<h2 className="text-xl font-semibold">
|
<div>test</div>
|
||||||
{currentStep?.title || 'Select a step'}
|
<Button size="sm" className='' onClick={() => setAddFieldDialogOpen(true)}>
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
{selectedSection && (
|
|
||||||
<Button size="sm" onClick={() => setAddFieldDialogOpen(true)}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Add Field
|
Add Field
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* Fields */}
|
||||||
{currentStep?.description && (
|
|
||||||
<p className="text-muted-foreground mb-6">{currentStep.description}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Sections and Fields */}
|
|
||||||
{sortedSections.map((section) => {
|
|
||||||
const sortedFields = section.fields?.sort((a, b) => a.order - b.order) || [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={section.id}
|
className='mb-6 p-4 rounded-lg border-2 border-dashed bg-background border-gray-200'
|
||||||
className={`mb-6 p-4 rounded-lg border-2 ${selectedSection === section.id
|
|
||||||
? 'border-brand-purple'
|
|
||||||
: 'border-dashed border-gray-200'
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedSection(section.id)}
|
|
||||||
>
|
>
|
||||||
<h3 className="text-lg font-medium mb-4">{section.title}</h3>
|
<h3 className="text-lg font-medium mb-4">title</h3>
|
||||||
{section.description && (
|
|
||||||
<p className="text-sm text-muted-foreground mb-4">{section.description}</p>
|
<p className="text-sm text-muted-foreground mb-4">{section.description}</p>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Fields */}
|
{/* Fields */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -692,7 +668,7 @@ const AdminRegistrationBuilder = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-6 w-6 text-red-500 hover:text-red-700"
|
className="h-6 w-6 text-red-500 hover-text-background"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDeleteField(field.id);
|
handleDeleteField(field.id);
|
||||||
@@ -702,10 +678,12 @@ const AdminRegistrationBuilder = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
|
||||||
{sortedFields.length === 0 && (
|
{sortedFields.length === 0 && (
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
No fields in this section. Click "Add Field" to add one.
|
No fields in this section. Click "Add Field" to add one.
|
||||||
@@ -713,23 +691,21 @@ const AdminRegistrationBuilder = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{sortedSections.length === 0 && (
|
|
||||||
<div className="text-center py-12 text-muted-foreground">
|
|
||||||
No sections in this step. Add a section from the left sidebar.
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||||
|
|
||||||
{/* Right Sidebar - Field Properties */}
|
{/* Right Sidebar - Field Properties */}
|
||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<h2 className="text-lg font-semibold mb-4">Field Properties</h2>
|
<h2 className="text-lg font-semibold mb-4">Edit Options</h2>
|
||||||
|
|
||||||
{selectedFieldData ? (
|
{selectedFieldData ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -1038,7 +1014,7 @@ const AdminRegistrationBuilder = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-6 w-6 text-red-500"
|
className="h-6 w-6 text-red-500 hover-text-background"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateSchema((prev) => ({
|
updateSchema((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
|||||||
Reference in New Issue
Block a user