diff --git a/src/app/admin/settings/CalendarIntegrations.tsx b/src/app/admin/settings/CalendarIntegrations.tsx index f55711c..d5538fc 100644 --- a/src/app/admin/settings/CalendarIntegrations.tsx +++ b/src/app/admin/settings/CalendarIntegrations.tsx @@ -14,6 +14,8 @@ import type { CalendarItem } from '@/app/admin/settings/components/CalendarForm' import CalendarOptionsList from '@/app/admin/settings/components/CalendarForm'; import SectionDivider from '@/app/admin/settings/components/SectionDivider'; import SectionHeader from '@/app/admin/settings/components/SectionHeader'; +import ProFeatureModal from '@/components/ui/ProFeatureModal'; +import { useAppSelector } from '@/redux/hooks'; import theme from '@/theme'; const InfoRow = styled(Box)({ @@ -45,15 +47,18 @@ const IconAndContentRow = styled(Box)({ gap: theme.spacing(1.5), }); -const ConnectButton = styled(Button)({ - backgroundColor: '#000000', +const ConnectButton = styled(Button, { + shouldForwardProp: prop => prop !== '$disabled', +})<{ $disabled?: boolean }>(({ $disabled }) => ({ + backgroundColor: $disabled ? '#e0e0e0' : '#000000', color: 'white', padding: `${theme.spacing(1)} ${theme.spacing(2)}`, textTransform: 'none', + boxShadow: 'none', '&:hover': { - backgroundColor: '#374151', + backgroundColor: $disabled ? '#e0e0e0' : '#374151', }, -}); +})); const RemoveButton = styled(Button)({ backgroundColor: 'transparent', @@ -87,7 +92,17 @@ const CustomCheckbox = styled(Checkbox)({ }, }); -export default function IntegrationsSection() { +interface IntegrationsSectionProps { + editable?: boolean; + showProBadge?: boolean; +} + +export default function IntegrationsSection({ + editable = false, + showProBadge = false, +}: IntegrationsSectionProps) { + const user = useAppSelector(state => state.auth.user); + const [showProModal, setShowProModal] = useState(false); const [isConnected, setIsConnected] = useState(false); const [showGoogleEvents, setShowGoogleEvents] = useState(true); const [calendars, setCalendars] = useState([ @@ -107,12 +122,21 @@ export default function IntegrationsSection() { }, ]); + const handleUnlockPro = () => setShowProModal(true); + const handleCloseProModal = () => setShowProModal(false); + const handleUpgrade = () => { + // Redirect to billing or upgrade page + window.location.href = '/admin/billing'; + }; + const handleConnect = () => { + if (!editable) return; // TODO: Implement Google Calendar OAuth connection setIsConnected(true); }; const handleRemove = () => { + if (!editable) return; setIsConnected(false); setShowGoogleEvents(true); setCalendars(prev => @@ -124,6 +148,7 @@ export default function IntegrationsSection() { }; const handleCalendarToggle = (calendarId: string) => { + if (!editable) return; setCalendars(prev => prev.map(cal => cal.id === calendarId ? { ...cal, checked: !cal.checked } : cal, @@ -134,7 +159,29 @@ export default function IntegrationsSection() { return ( <> - + + + {showProBadge && !editable && ( + + Pro + PRO + + )} + @@ -151,7 +198,7 @@ export default function IntegrationsSection() { /> - email51@company.com + {user?.email ?? ''} Sync your appointments to Google Calendar. Online booking @@ -163,9 +210,35 @@ export default function IntegrationsSection() { {isConnected ? ( - Remove + + Remove + + ) : editable ? ( + + Connect + ) : ( - Connect + )} @@ -175,15 +248,18 @@ export default function IntegrationsSection() { Connected account: - email51@company.com + {user?.email ?? ''} setShowGoogleEvents(e.target.checked)} + onChange={e => { + if (editable) setShowGoogleEvents(e.target.checked); + }} size="small" + disabled={!editable} /> } label={ @@ -202,10 +278,19 @@ export default function IntegrationsSection() { )} + + {/* Render the modal here */} + ); } diff --git a/src/app/admin/settings/CompanyInfo.tsx b/src/app/admin/settings/CompanyInfo.tsx index 63fdb2f..cc4edf3 100644 --- a/src/app/admin/settings/CompanyInfo.tsx +++ b/src/app/admin/settings/CompanyInfo.tsx @@ -1,7 +1,11 @@ 'use client'; -import React from 'react'; +import { Box } from '@mui/material'; +import Image from 'next/image'; +import React, { useState } from 'react'; import EditableSection from '@/app/admin/settings/components/EditableSection'; +import SectionHeader from '@/app/admin/settings/components/SectionHeader'; +import ProFeatureModal from '@/components/ui/ProFeatureModal'; import { useCheckABNExistsMutation, useGetCompanyInfoQuery, @@ -62,9 +66,17 @@ const validateABNFormat = (abn: string): ValidationResult => { return { isValid: true }; }; -export default function CompanyInfoSection() { +export default function CompanyInfoSection({ + editable = false, + showProBadge = false, +}: { editable?: boolean; showProBadge?: boolean } = {}) { const user = useAppSelector(state => state.auth.user); const [checkABNExists] = useCheckABNExistsMutation(); + const [showProModal, setShowProModal] = useState(false); + const handleCloseProModal = () => setShowProModal(false); + const handleUpgrade = () => { + window.location.href = '/admin/billing'; + }; // Synchronous validation for real-time feedback (format only) const validateABN = (abn: string): ValidationResult => { @@ -119,30 +131,71 @@ export default function CompanyInfoSection() { } : undefined; + // Handler for edit button + const handleEditClick = () => { + setShowProModal(true); + }; + + const titleWithBadge = ( + + + {showProBadge && !editable && ( + + Pro + PRO + + )} + + ); + return ( - + <> + + + ); } diff --git a/src/app/admin/settings/SettingsSection.tsx b/src/app/admin/settings/SettingsSection.tsx index a9270ae..d980cfc 100644 --- a/src/app/admin/settings/SettingsSection.tsx +++ b/src/app/admin/settings/SettingsSection.tsx @@ -9,7 +9,9 @@ import CompanyInfoSection from '@/app/admin/settings/CompanyInfo'; import GreetingSection from '@/app/admin/settings/GreetingSection'; import UserProfileSection from '@/app/admin/settings/UserProfileSection'; import VerificationSection from '@/app/admin/settings/VerificationSection'; +import { useSubscription } from '@/features/subscription/useSubscription'; import theme from '@/theme'; +import { getPlanTier, isProPlan } from '@/utils/planUtils'; const SettingsContainer = styled(Box)({ padding: theme.spacing(3, 4), @@ -34,13 +36,16 @@ const SettingsContainer = styled(Box)({ }); export default function SettingsSection() { + const { subscription } = useSubscription(); + const planTier = getPlanTier(subscription); + const isPro = isProPlan(planTier); return ( - - + + ); diff --git a/src/app/admin/settings/components/CalendarForm.tsx b/src/app/admin/settings/components/CalendarForm.tsx index 5523577..98428cd 100644 --- a/src/app/admin/settings/components/CalendarForm.tsx +++ b/src/app/admin/settings/components/CalendarForm.tsx @@ -84,12 +84,14 @@ interface CalendarOptionsListProps { calendars: CalendarItem[]; onToggle: (calendarId: string) => void; title?: string; + editable?: boolean; } export default function CalendarOptionsList({ calendars, onToggle, title = 'Calendars to show:', + editable = false, }: CalendarOptionsListProps) { return ( <> @@ -105,8 +107,9 @@ export default function CalendarOptionsList({ onToggle(calendar.id)} + onChange={() => editable && onToggle(calendar.id)} size="small" + disabled={!editable} /> ))} diff --git a/src/app/admin/settings/components/EditModal.tsx b/src/app/admin/settings/components/EditModal.tsx index f9b9d5a..d6af8d2 100644 --- a/src/app/admin/settings/components/EditModal.tsx +++ b/src/app/admin/settings/components/EditModal.tsx @@ -11,7 +11,7 @@ import React from 'react'; interface EditModalProps { open: boolean; - title: string; + title: React.ReactNode; onClose: () => void; onSave: () => void; children: React.ReactNode; diff --git a/src/app/admin/settings/components/EditableSection.tsx b/src/app/admin/settings/components/EditableSection.tsx index bdd7958..d91e679 100644 --- a/src/app/admin/settings/components/EditableSection.tsx +++ b/src/app/admin/settings/components/EditableSection.tsx @@ -52,7 +52,7 @@ interface Field { } interface EditableSectionProps { - title: string; + title: React.ReactNode; fields: Field[] | ((values: Record) => Field[]); initialValues?: Record; columns?: number; @@ -60,6 +60,7 @@ interface EditableSectionProps { data?: Record; isLoading?: boolean; onSave?: (values: Record) => Promise; + onEdit?: () => void; } function splitFields(fields: Field[], columns: number) { @@ -80,6 +81,7 @@ export default function EditableSection({ data, isLoading = false, onSave, + onEdit, }: EditableSectionProps) { const [open, setOpen] = React.useState(false); @@ -105,7 +107,7 @@ export default function EditableSection({ setOpen(true); }; - const handleSave = async (values: Record) => { + const handleSave = async () => { setError(''); setSaving(true); @@ -160,7 +162,7 @@ export default function EditableSection({ {fieldColumns.map((colFields, colIdx) => ( @@ -183,7 +185,7 @@ export default function EditableSection({ setOpen(false); setError(''); }} - onSave={() => void handleSave(formValues)} + onSave={() => void handleSave()} > {error && ( diff --git a/src/app/admin/settings/components/SectionHeader.tsx b/src/app/admin/settings/components/SectionHeader.tsx index 15b8127..3aad7f3 100644 --- a/src/app/admin/settings/components/SectionHeader.tsx +++ b/src/app/admin/settings/components/SectionHeader.tsx @@ -8,7 +8,7 @@ import React from 'react'; import theme from '@/theme'; interface SectionHeaderProps { - title: string; + title: React.ReactNode; onEdit?: () => void; showEditIcon?: boolean; } @@ -31,6 +31,8 @@ const SectionHeader: React.FC = ({ onEdit, showEditIcon = true, }) => { + // Safe string version of title for places where a string is required (e.g. aria-labels). + const titleText = typeof title === 'string' ? title : 'section'; return ( {title} @@ -40,7 +42,7 @@ const SectionHeader: React.FC = ({