Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
76
legacy_reference/frontend/src/pages/PublicSitePage.tsx
Normal file
76
legacy_reference/frontend/src/pages/PublicSitePage.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
// FIX: PageComponent will be imported from types after the type definition is added.
|
||||
import { Business, PageComponent } from '../types';
|
||||
import { SERVICES } from '../mockData';
|
||||
|
||||
const RenderComponent: React.FC<{ component: PageComponent }> = ({ component }) => {
|
||||
switch (component.type) {
|
||||
case 'HEADING': {
|
||||
// FIX: Replaced dynamic JSX tag with React.createElement to fix parsing errors.
|
||||
const tag = `h${component.content?.level || 1}`;
|
||||
const className = `font-bold text-gray-900 dark:text-white my-4 ${
|
||||
component.content?.level === 1 ? 'text-4xl' :
|
||||
component.content?.level === 2 ? 'text-2xl' : 'text-xl'
|
||||
}`;
|
||||
return React.createElement(tag, { className }, component.content?.text);
|
||||
}
|
||||
case 'TEXT':
|
||||
return <p className="text-gray-600 dark:text-gray-300 my-4 leading-relaxed">{component.content?.text}</p>;
|
||||
case 'IMAGE':
|
||||
return <img src={component.content?.src} alt={component.content?.alt} className="rounded-lg my-4 max-w-full h-auto shadow-md" />;
|
||||
case 'BUTTON':
|
||||
return <a href={component.content?.href} className="inline-block px-6 py-3 bg-brand-600 text-white font-semibold rounded-lg hover:bg-brand-700 my-4 shadow-sm transition-colors">{component.content?.buttonText}</a>;
|
||||
case 'SERVICE':
|
||||
const service = SERVICES.find(s => s.id === component.content?.serviceId);
|
||||
if (!service) return <div className="text-red-500">Service not found</div>;
|
||||
return (
|
||||
<div className="p-6 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 my-4 shadow-sm">
|
||||
<h4 className="text-xl font-bold">{service.name}</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">{service.description}</p>
|
||||
<div className="flex items-center justify-between mt-4">
|
||||
<span className="text-lg font-bold text-gray-900 dark:text-white">${service.price.toFixed(2)}</span>
|
||||
<Link to="/portal/book" className="text-sm font-medium text-brand-600 hover:underline">Book Now →</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'COLUMNS':
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row gap-8 my-4">
|
||||
{component.children?.map((col, colIndex) => (
|
||||
<div key={colIndex} className="flex-1 space-y-4">
|
||||
{col.map(child => <RenderComponent key={child.id} component={child} />)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
interface PublicSitePageProps {
|
||||
business: Business;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const PublicSitePage: React.FC<PublicSitePageProps> = ({ business, path }) => {
|
||||
// FIX: Property 'websitePages' is optional. Added optional chaining.
|
||||
const page = business.websitePages?.[path] || business.websitePages?.['/'];
|
||||
|
||||
if (!page) {
|
||||
return <div>Page not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{page.content.map(component => (
|
||||
<RenderComponent key={component.id} component={component} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicSitePage;
|
||||
Reference in New Issue
Block a user