cleanup codeigniter code
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { getCiMeta } from '../lib/ciRouteLookup'
|
||||
|
||||
/** Fallback for CI-style paths not yet ported to dedicated React pages. */
|
||||
export function CiPlaceholderPage() {
|
||||
const params = useParams()
|
||||
const star = params['*'] ?? ''
|
||||
const segments = star.split('/').filter(Boolean)
|
||||
const meta = getCiMeta(star)
|
||||
|
||||
const title = meta?.title ?? 'Section placeholder'
|
||||
return (
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/app/home">Home</Link>
|
||||
</li>
|
||||
{segments.map((s, i) => (
|
||||
<li
|
||||
key={`${s}-${i}`}
|
||||
className={`breadcrumb-item ${i === segments.length - 1 ? 'active' : ''}`}
|
||||
aria-current={i === segments.length - 1 ? 'page' : undefined}
|
||||
>
|
||||
{s}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h2 className="h4 mb-3">{title}</h2>
|
||||
|
||||
<p className="text-muted small mb-3">
|
||||
Implement this screen using the matching routes in <code>app_laravel/routes/api.php</code>{' '}
|
||||
(JWT <code>Authorization: Bearer</code>). See also <Link to="/docs">API docs</Link>.
|
||||
</p>
|
||||
<p className="small mb-3">
|
||||
SPA path: <code>/app/{star || '…'}</code>
|
||||
</p>
|
||||
<p className="small mb-0">
|
||||
<Link to="/app/browse">Browse all CI view routes</Link>
|
||||
{' · '}
|
||||
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { type FormEvent, useEffect, useRef, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { requestForgotPassword } from '../api/passwordFlow'
|
||||
|
||||
/** Mirrors CodeIgniter `user/forgot_password.php` + confirmation modal from `password_reset_confirmation.php`. */
|
||||
export function ForgotPasswordPage() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* Ported from CodeIgniter `Views/index.php` — scoped to avoid overriding global layout. */
|
||||
.home-landing {
|
||||
--home-primary: #3a8fd1;
|
||||
--home-secondary: #2c5e8a;
|
||||
|
||||
@@ -12,7 +12,7 @@ function userMayLoadAdministratorHomeMetrics(user: AuthUser | null): boolean {
|
||||
return Boolean(r.administrator || r.admin || r.super_admin)
|
||||
}
|
||||
|
||||
/** Carousel + sections ported from CodeIgniter `Views/index.php` (Al Rahma public home). */
|
||||
/** Carousel + sections adapted from the previous public home content. */
|
||||
const CAROUSEL_SLIDES = [
|
||||
{
|
||||
image: '/images/carousel-1.png',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { fetchDashboardRoute } from '../api/session'
|
||||
import { useAuth } from '../auth/AuthProvider'
|
||||
import { spaPathFromCiUrl } from '../lib/ciSpaPaths'
|
||||
|
||||
/** Matches CodeIgniter `Views/user/login.php` (JWT via `/api/v1/auth/login`). */
|
||||
export function LoginPage() {
|
||||
const { login } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -15,7 +15,6 @@ function formatUsPhone(input: string): string {
|
||||
return `${raw.slice(0, 3)}-${raw.slice(3, 6)}-${raw.slice(6)}`
|
||||
}
|
||||
|
||||
/** Matches CodeIgniter `Views/user/register.php` + Laravel `POST /api/v1/auth/register` (captcha flow). */
|
||||
export function RegisterPage() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { getViewRouteMeta } from '../lib/viewRouteLookup'
|
||||
|
||||
/** Fallback for mapped source-view paths not yet ported to dedicated React pages. */
|
||||
export function ViewRoutePlaceholderPage() {
|
||||
const params = useParams()
|
||||
const star = params['*'] ?? ''
|
||||
const segments = star.split('/').filter(Boolean)
|
||||
const meta = getViewRouteMeta(star)
|
||||
|
||||
const title = meta?.title ?? 'Section placeholder'
|
||||
return (
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/app/home">Home</Link>
|
||||
</li>
|
||||
{segments.map((segment, index) => (
|
||||
<li
|
||||
key={
|
||||
segment + '-' + index
|
||||
}
|
||||
className={
|
||||
'breadcrumb-item ' + (index === segments.length - 1 ? 'active' : '')
|
||||
}
|
||||
aria-current={index === segments.length - 1 ? 'page' : undefined}
|
||||
>
|
||||
{segment}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h2 className="h4 mb-3">{title}</h2>
|
||||
|
||||
<p className="text-muted small mb-3">
|
||||
Implement this screen using the matching Laravel routes and API endpoints (JWT{' '}
|
||||
<code>Authorization: Bearer</code>). See also <Link to="/docs">API docs</Link>.
|
||||
</p>
|
||||
<p className="small mb-3">
|
||||
SPA path: <code>/app/{star || '…'}</code>
|
||||
</p>
|
||||
<p className="small mb-0">
|
||||
<Link to="/app/browse">Browse all mapped view routes</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +1,59 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ciRouteMeta } from '../lib/ciRouteMeta.generated'
|
||||
import { ciRegistryKeyToAppPath } from '../lib/ciRouteLookup'
|
||||
|
||||
/** Browse every CodeIgniter view file mapped under `/app/…` (parity placeholders unless a dedicated route exists). */
|
||||
export function CiSitemapPage() {
|
||||
const grouped = useMemo(() => {
|
||||
const g = new Map<string, { key: string; title: string }[]>()
|
||||
for (const key of Object.keys(ciRouteMeta).sort()) {
|
||||
const top = key.includes('/') ? key.slice(0, key.indexOf('/')) : key
|
||||
const title = ciRouteMeta[key]?.title ?? key
|
||||
const list = g.get(top) ?? []
|
||||
list.push({ key, title })
|
||||
g.set(top, list)
|
||||
}
|
||||
return [...g.entries()].sort(([a], [b]) => a.localeCompare(b))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/app/home">Home</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active" aria-current="page">
|
||||
CI views
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<p className="text-muted small mb-4">
|
||||
Generated from <code>app_codeigniter/app/Views/**/*.php</code>. Each link opens the parity
|
||||
route under <code>/app/…</code>; unimplemented screens use the section placeholder with the
|
||||
matching view path. Regenerate: <code>npm run gen:ci-meta</code>.
|
||||
</p>
|
||||
|
||||
<div className="row g-4">
|
||||
{grouped.map(([section, items]) => (
|
||||
<div key={section} className="col-12 col-md-6 col-xl-4">
|
||||
<div className="border rounded bg-white shadow-sm">
|
||||
<div className="px-3 py-2 border-bottom small fw-semibold text-muted">{section}</div>
|
||||
<ul className="list-unstyled mb-0 px-2 py-2" style={{ maxHeight: 280, overflow: 'auto' }}>
|
||||
{items.map(({ key, title }) => (
|
||||
<li key={key} className="small py-1">
|
||||
<Link to={ciRegistryKeyToAppPath(key)}>{title}</Link>
|
||||
<div className="text-muted" style={{ fontSize: '0.75rem' }}>
|
||||
<code>{key.replace(/\//g, ' / ')}</code>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { useMemo } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { viewRouteMeta } from '../lib/viewRouteMeta.generated'
|
||||
import { registryKeyToAppPath } from '../lib/viewRouteLookup'
|
||||
|
||||
export function ViewRouteSitemapPage() {
|
||||
const grouped = useMemo(() => {
|
||||
const groups = new Map<string, { key: string; title: string }[]>()
|
||||
for (const key of Object.keys(viewRouteMeta).sort()) {
|
||||
const top = key.includes('/') ? key.slice(0, key.indexOf('/')) : key
|
||||
const title = viewRouteMeta[key]?.title ?? key
|
||||
const list = groups.get(top) ?? []
|
||||
list.push({ key, title })
|
||||
groups.set(top, list)
|
||||
}
|
||||
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/app/home">Home</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active" aria-current="page">
|
||||
View routes
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<p className="text-muted small mb-4">
|
||||
Generated from discovered PHP view sources. Each link opens the mapped route under{' '}
|
||||
<code>/app/…</code>; unimplemented screens use the section placeholder with the matching
|
||||
source view path. Regenerate: <code>npm run gen:view-meta</code>.
|
||||
</p>
|
||||
|
||||
<div className="row g-4">
|
||||
{grouped.map(([section, items]) => (
|
||||
<div key={section} className="col-12 col-md-6 col-xl-4">
|
||||
<div className="border rounded bg-white shadow-sm">
|
||||
<div className="px-3 py-2 border-bottom small fw-semibold text-muted">{section}</div>
|
||||
<ul className="list-unstyled mb-0 px-2 py-2" style={{ maxHeight: 280, overflow: 'auto' }}>
|
||||
{items.map(({ key, title }) => (
|
||||
<li key={key} className="small py-1">
|
||||
<Link to={registryKeyToAppPath(key)}>{title}</Link>
|
||||
<div className="text-muted" style={{ fontSize: '0.75rem' }}>
|
||||
<code>{key.replace(/\//g, ' / ')}</code>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* Mirrors CodeIgniter `layout/email_layout.php` — scoped for admin preview only */
|
||||
.email-preview-root {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f9f9f9;
|
||||
|
||||
@@ -26,7 +26,7 @@ export function EmailTemplatePreviewPage() {
|
||||
<h2 className="h5 mb-0">{entry.title}</h2>
|
||||
</div>
|
||||
<p className="small text-muted mb-3">
|
||||
CI: <code>{entry.ciView}</code>
|
||||
Source template: <code>{entry.sourceTemplatePath}</code>
|
||||
</p>
|
||||
<EmailPreviewLayout>{entry.render()}</EmailPreviewLayout>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ export function EmailTemplatesIndexPage() {
|
||||
<div className="container-fluid py-3">
|
||||
<h2 className="h4 mb-3">Email templates (preview)</h2>
|
||||
<p className="text-muted small mb-4">
|
||||
React previews matching CodeIgniter <code>Views/emails/*.php</code>. Laravel should render the same HTML for
|
||||
outgoing mail; this UI is for QA and copy review.
|
||||
React previews matching the current PHP email templates. Laravel should render the same HTML
|
||||
for outgoing mail; this UI is for QA and copy review.
|
||||
</p>
|
||||
<div className="mb-3">
|
||||
<Link to="/app/administrator/parent-email-extractor" className="btn btn-outline-primary btn-sm">
|
||||
@@ -21,7 +21,7 @@ export function EmailTemplatesIndexPage() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Template</th>
|
||||
<th>CI view</th>
|
||||
<th>Source template</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -32,10 +32,12 @@ export function EmailTemplatesIndexPage() {
|
||||
<tr key={slug}>
|
||||
<td>{row.title}</td>
|
||||
<td>
|
||||
<code className="small">{row.ciView}</code>
|
||||
<code className="small">{row.sourceTemplatePath}</code>
|
||||
</td>
|
||||
<td className="text-end">
|
||||
<Link className="btn btn-sm btn-outline-secondary" to={`/app/administrator/email-templates/${slug}`}>
|
||||
<Link className="btn btn-sm btn-outline-secondary" to={
|
||||
'/app/administrator/email-templates/' + slug
|
||||
}>
|
||||
Preview
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
@@ -71,164 +71,164 @@ export type EmailTemplateSlug =
|
||||
|
||||
export type RegistryEntry = {
|
||||
title: string
|
||||
ciView: string
|
||||
sourceTemplatePath: string
|
||||
render: () => JSX.Element
|
||||
}
|
||||
|
||||
export const EMAIL_TEMPLATE_REGISTRY: Record<EmailTemplateSlug, RegistryEntry> = {
|
||||
'final-warning': {
|
||||
title: 'Final attendance warning',
|
||||
ciView: 'app/Views/emails/final_warning.php',
|
||||
sourceTemplatePath: 'emails/final_warning.php',
|
||||
render: () => <FinalWarningBody />,
|
||||
},
|
||||
'follow-up': {
|
||||
title: 'Attendance follow-up',
|
||||
ciView: 'app/Views/emails/follow_up.php',
|
||||
sourceTemplatePath: 'emails/follow_up.php',
|
||||
render: () => <FollowUpBody />,
|
||||
},
|
||||
dismissal: {
|
||||
title: 'Dismissal (absences)',
|
||||
ciView: 'app/Views/emails/dismissal.php',
|
||||
sourceTemplatePath: 'emails/dismissal.php',
|
||||
render: () => <DismissalBody />,
|
||||
},
|
||||
'parent-attendance-admin': {
|
||||
title: 'Parent attendance — admin notification',
|
||||
ciView: 'app/Views/emails/parent_attendance_admin.php',
|
||||
sourceTemplatePath: 'emails/parent_attendance_admin.php',
|
||||
render: () => <ParentAttendanceAdminBody />,
|
||||
},
|
||||
'parent-attendance-parent': {
|
||||
title: 'Parent attendance — confirmation to parent',
|
||||
ciView: 'app/Views/emails/parent_attendance_parent.php',
|
||||
sourceTemplatePath: 'emails/parent_attendance_parent.php',
|
||||
render: () => <ParentAttendanceParentBody />,
|
||||
},
|
||||
'below-sixty-performance': {
|
||||
title: 'Below 60 performance',
|
||||
ciView: 'app/Views/emails/below_sixty_performance.php',
|
||||
sourceTemplatePath: 'emails/below_sixty_performance.php',
|
||||
render: () => <BelowSixtyPerformanceBody />,
|
||||
},
|
||||
'payment-receipt': {
|
||||
title: 'Payment receipt',
|
||||
ciView: 'app/Views/emails/payment_receipt.php',
|
||||
sourceTemplatePath: 'emails/payment_receipt.php',
|
||||
render: () => <PaymentReceiptBody />,
|
||||
},
|
||||
'extra-charge-notice': {
|
||||
title: 'Extra charge notice',
|
||||
ciView: 'app/Views/emails/extra_charge_notice.php',
|
||||
sourceTemplatePath: 'emails/extra_charge_notice.php',
|
||||
render: () => <ExtraChargeNoticeBody />,
|
||||
},
|
||||
'reset-password': {
|
||||
title: 'Reset password',
|
||||
ciView: 'app/Views/emails/reset_password.php',
|
||||
sourceTemplatePath: 'emails/reset_password.php',
|
||||
render: () => <ResetPasswordBody />,
|
||||
},
|
||||
'welcome-parent': {
|
||||
title: 'Welcome — parent activation',
|
||||
ciView: 'app/Views/emails/welcome_parent.php',
|
||||
sourceTemplatePath: 'emails/welcome_parent.php',
|
||||
render: () => <WelcomeParentBody />,
|
||||
},
|
||||
'welcome-staff': {
|
||||
title: 'Welcome — staff activation',
|
||||
ciView: 'app/Views/emails/welcome_staff.php',
|
||||
sourceTemplatePath: 'emails/welcome_staff.php',
|
||||
render: () => <WelcomeStaffBody />,
|
||||
},
|
||||
'welcome-user': {
|
||||
title: 'Welcome — generic user',
|
||||
ciView: 'app/Views/emails/welcome_user.php',
|
||||
sourceTemplatePath: 'emails/welcome_user.php',
|
||||
render: () => <WelcomeUserBody />,
|
||||
},
|
||||
'student-removed': {
|
||||
title: 'Student removed',
|
||||
ciView: 'app/Views/emails/student_removed.php',
|
||||
sourceTemplatePath: 'emails/student_removed.php',
|
||||
render: () => <StudentRemovedBody />,
|
||||
},
|
||||
'support-new-account': {
|
||||
title: 'Support — new account alert',
|
||||
ciView: 'app/Views/emails/support_new_account.php',
|
||||
sourceTemplatePath: 'emails/support_new_account.php',
|
||||
render: () => <SupportNewAccountBody />,
|
||||
},
|
||||
'admin-student-registered': {
|
||||
title: 'Admin — student registered',
|
||||
ciView: 'app/Views/emails/admin_student_registered.php',
|
||||
sourceTemplatePath: 'emails/admin_student_registered.php',
|
||||
render: () => <AdminStudentRegisteredBody />,
|
||||
},
|
||||
'status-admission-review': {
|
||||
title: 'Status — admission under review',
|
||||
ciView: 'app/Views/emails/status_admission_review.php',
|
||||
sourceTemplatePath: 'emails/status_admission_review.php',
|
||||
render: () => <StatusAdmissionReviewBody />,
|
||||
},
|
||||
'status-denied': {
|
||||
title: 'Status — admission denied',
|
||||
ciView: 'app/Views/emails/status_denied.php',
|
||||
sourceTemplatePath: 'emails/status_denied.php',
|
||||
render: () => <StatusDeniedBody />,
|
||||
},
|
||||
'status-enrolled': {
|
||||
title: 'Status — enrolled',
|
||||
ciView: 'app/Views/emails/status_enrolled.php',
|
||||
sourceTemplatePath: 'emails/status_enrolled.php',
|
||||
render: () => <StatusEnrolledBody />,
|
||||
},
|
||||
'status-not-enrolled': {
|
||||
title: 'Status — not enrolled',
|
||||
ciView: 'app/Views/emails/status_not_enrolled.php',
|
||||
sourceTemplatePath: 'emails/status_not_enrolled.php',
|
||||
render: () => <StatusNotEnrolledBody />,
|
||||
},
|
||||
'status-payment-pending': {
|
||||
title: 'Status — payment pending',
|
||||
ciView: 'app/Views/emails/status_payment_pending.php',
|
||||
sourceTemplatePath: 'emails/status_payment_pending.php',
|
||||
render: () => <StatusPaymentPendingBody />,
|
||||
},
|
||||
'status-refund-pending': {
|
||||
title: 'Status — refund pending',
|
||||
ciView: 'app/Views/emails/status_refund_pending.php',
|
||||
sourceTemplatePath: 'emails/status_refund_pending.php',
|
||||
render: () => <StatusRefundPendingBody />,
|
||||
},
|
||||
'status-waitlist': {
|
||||
title: 'Status — waitlist',
|
||||
ciView: 'app/Views/emails/status_waitlist.php',
|
||||
sourceTemplatePath: 'emails/status_waitlist.php',
|
||||
render: () => <StatusWaitlistBody />,
|
||||
},
|
||||
'status-withdraw-review': {
|
||||
title: 'Status — withdraw under review',
|
||||
ciView: 'app/Views/emails/status_withdraw_review.php',
|
||||
sourceTemplatePath: 'emails/status_withdraw_review.php',
|
||||
render: () => <StatusWithdrawReviewBody />,
|
||||
},
|
||||
'status-withdrawn': {
|
||||
title: 'Status — withdrawn',
|
||||
ciView: 'app/Views/emails/status_withdrawn.php',
|
||||
sourceTemplatePath: 'emails/status_withdrawn.php',
|
||||
render: () => <StatusWithdrawnBody />,
|
||||
},
|
||||
'calendar-notification': {
|
||||
title: 'Calendar notification',
|
||||
ciView: 'app/Views/emails/calendar_notification.php',
|
||||
sourceTemplatePath: 'emails/calendar_notification.php',
|
||||
render: () => <CalendarNotificationBody />,
|
||||
},
|
||||
'event-broadcast': {
|
||||
title: 'Event broadcast',
|
||||
ciView: 'app/Views/emails/event_broadcast.php',
|
||||
sourceTemplatePath: 'emails/event_broadcast.php',
|
||||
render: () => <EventBroadcastBody />,
|
||||
},
|
||||
'custom-html': {
|
||||
title: 'Custom HTML body',
|
||||
ciView: 'app/Views/emails/custom_html.php',
|
||||
sourceTemplatePath: 'emails/custom_html.php',
|
||||
render: () => <CustomHtmlBody />,
|
||||
},
|
||||
'wrap-layout-sample': {
|
||||
title: 'Wrap layout ($body_html)',
|
||||
ciView: 'app/Views/emails/_wrap_layout.php',
|
||||
sourceTemplatePath: 'emails/_wrap_layout.php',
|
||||
render: () => <WrapLayoutSampleBody />,
|
||||
},
|
||||
'broadcast-wrapper-sample': {
|
||||
title: 'Broadcast wrapper ($content)',
|
||||
ciView: 'app/Views/emails/broadcast_wrapper.php',
|
||||
sourceTemplatePath: 'emails/broadcast_wrapper.php',
|
||||
render: () => <BroadcastWrapperSampleBody />,
|
||||
},
|
||||
'whatsapp-invite': {
|
||||
title: 'WhatsApp invite',
|
||||
ciView: 'app/Views/emails/whatsapp_invite.php',
|
||||
sourceTemplatePath: 'emails/whatsapp_invite.php',
|
||||
render: () => <WhatsAppInviteBody />,
|
||||
},
|
||||
'whatsapp-group-invitation': {
|
||||
title: 'WhatsApp group invitation (draft text)',
|
||||
ciView: 'app/Views/emails/whatsapp_group_invitation.php',
|
||||
sourceTemplatePath: 'emails/whatsapp_group_invitation.php',
|
||||
render: () => <WhatsAppGroupInvitationDraftBody />,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
/** Port of CodeIgniter `Views/errors/access_denied.php`. */
|
||||
|
||||
export function AccessDeniedPage() {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
/** Port of CodeIgniter `Views/errors/invalid_token.php` (registration token invalid/expired). */
|
||||
export function InvalidTokenPage() {
|
||||
return (
|
||||
<div className="registration-form container mt-5 mb-5">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Vendor / retailor UX from CodeIgniter `expenses/create.php` and `edit.php`
|
||||
* (dropdown + Other text field).
|
||||
*/
|
||||
export function ExpenseVendorFields({
|
||||
retailors,
|
||||
initialRetailor,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export type ParentParitySpec = {
|
||||
title: string
|
||||
/** File name under `app/Views/parent/` (e.g. `calendar.php`). */
|
||||
ciViewFile: string
|
||||
/** Example CodeIgniter URL paths (with leading slash). */
|
||||
legacyCiRoutes?: string[]
|
||||
}
|
||||
|
||||
title: string
|
||||
/** File name under `app/Views/parent/` (e.g. `calendar.php`). */
|
||||
ciViewFile: string
|
||||
legacyCiRoutes?: string[]
|
||||
}
|
||||
|
||||
type ShellProps = ParentParitySpec & {
|
||||
children?: ReactNode
|
||||
fullWidth?: boolean
|
||||
@@ -20,12 +19,12 @@ export function ParentParityShell({
|
||||
}: ShellProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="text-center mb-3">
|
||||
<h2 className="h4 text-success" style={{ fontFamily: 'Arial, sans-serif' }}>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
</div>
|
||||
<div className="text-center mb-3">
|
||||
<h2 className="h4 text-success" style={{ fontFamily: 'Arial, sans-serif' }}>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="row g-3">
|
||||
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
/** Barrel for CodeIgniter `Views/teacher/*` React parity screens. */
|
||||
export * from './TeacherCorePages'
|
||||
export * from './TeacherRestPages'
|
||||
|
||||
Reference in New Issue
Block a user