Auto commit: 2026-06-03T16:46:23.603Z

This commit is contained in:
Flatlogic Bot 2026-06-03 16:46:23 +00:00
parent 9a1a1c5e57
commit ff296fb0be
5 changed files with 653 additions and 149 deletions

View File

@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Link from 'next/link';
import { useAppDispatch } from '../stores/hooks';
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react'
import React, { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'

View File

@ -1,166 +1,675 @@
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import { FormEvent, ReactElement, useMemo, useState } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
type ClassroomMode = 'Live Classroom' | 'AI Exam Hall' | 'Parent Conference' | 'Teacher Studio';
type LanguageMode = 'Arabic + English' | 'English only' | 'Arabic only';
type StorageProfile = 'Google Drive vault' | 'Local encrypted archive' | 'Hybrid mirrored backup';
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('image');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
type ClassroomPlan = {
id: string;
name: string;
supervisor: string;
mode: ClassroomMode;
languageMode: LanguageMode;
storageProfile: StorageProfile;
learners: number;
objective: string;
features: string[];
createdAt: string;
};
const title = 'Autonomous Video Platform'
const featureOptions = [
'AI proctoring',
'Mediasoup SFU',
'P2P fallback',
'Whiteboard canvas',
'Breakout rooms',
'Collaborative notes',
'Live polls',
'AI copilot',
'Recording pipeline',
'Gradebook sync',
];
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const modeAccent: Record<ClassroomMode, string> = {
'Live Classroom': 'from-cyan-300 to-blue-500',
'AI Exam Hall': 'from-amber-300 to-orange-500',
'Parent Conference': 'from-emerald-300 to-teal-500',
'Teacher Studio': 'from-fuchsia-300 to-violet-500',
};
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
const storageDescriptions: Record<StorageProfile, string> = {
'Google Drive vault': 'Uploads, recordings, backups, and static assets route to the BritishCE44 Drive vault after OAuth consent.',
'Local encrypted archive': 'Core services keep encrypted classroom artifacts on HTTPS localhost for offline-first operation.',
'Hybrid mirrored backup': 'Local PostgreSQL/Redis state remains authoritative while encrypted archives are mirrored to Google Drive.',
};
const starterClassrooms: ClassroomPlan[] = [
{
id: 'ce44-room-01',
name: 'CE44 English Foundation Room 01',
supervisor: 'Majed Kaid Alsharabi',
mode: 'Live Classroom',
languageMode: 'Arabic + English',
storageProfile: 'Hybrid mirrored backup',
learners: 32,
objective: 'Run a bilingual lesson with whiteboard practice, polls, recording, and AI-generated revision notes.',
features: ['Mediasoup SFU', 'Whiteboard canvas', 'AI copilot', 'Recording pipeline', 'Live polls'],
createdAt: 'Template',
},
{
id: 'ce44-exam-secure',
name: 'Secure AI Exam Hall',
supervisor: 'Academic Admin',
mode: 'AI Exam Hall',
languageMode: 'English only',
storageProfile: 'Google Drive vault',
learners: 80,
objective: 'Deliver monitored assessments with AI proctoring, identity checks, and bilingual report exports.',
features: ['AI proctoring', 'Mediasoup SFU', 'P2P fallback', 'Gradebook sync', 'Recording pipeline'],
createdAt: 'Template',
},
];
const deploymentTracks = [
['Web App', 'Active control-plane UI for classrooms, LMS planning, admin review, and browser-based collaboration workflows.'],
['Windows EXE', 'Packaging track for a local HTTPS server bundle with single consent prompt and offline-ready services.'],
['Android APK', 'Mobile classroom track for camera, microphone, Drive consent, learner attendance, and parent access.'],
];
const suiteCards = [
['40+ classrooms', 'Pre-modeled digital rooms for live lessons, exams, parent conferences, labs, clubs, and teacher studios.'],
['LMS core', 'Courses, assignments, gradebook, AI-graded tests, parent portal, and bilingual Arabic/English reports.'],
['AI-native class flow', 'Copilot summaries, proctoring signals, collaborative notes, live polls, and catch-up packs.'],
['Hybrid server model', 'HTTPS localhost for WebRTC/API/database with Google Drive for cloud storage, backups, and static assets.'],
['Admin-only expansion', 'Tables, sheets, modules, and settings are planned as expandable only by the authorized admin role.'],
['Media resilience', 'Mediasoup SFU-first architecture with P2P fallback, low-bandwidth mode, recording, and recovery hooks.'],
];
const buildClassroomId = (name: string) =>
name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
.slice(0, 36) || `classroom-${Date.now()}`;
const getCapacityPlan = (learnerCount: number) => {
if (learnerCount > 180) {
return 'Cascading SFU district mode';
}
if (learnerCount > 40) {
return 'Mediasoup SFU classroom cluster';
}
return 'SFU room with P2P fallback';
};
export default function BritishCE44Platform() {
const [classrooms, setClassrooms] = useState<ClassroomPlan[]>(starterClassrooms);
const [selectedClassroomId, setSelectedClassroomId] = useState(starterClassrooms[0].id);
const [name, setName] = useState('');
const [supervisor, setSupervisor] = useState('Majed Kaid Alsharabi');
const [mode, setMode] = useState<ClassroomMode>('Live Classroom');
const [languageMode, setLanguageMode] = useState<LanguageMode>('Arabic + English');
const [storageProfile, setStorageProfile] = useState<StorageProfile>('Hybrid mirrored backup');
const [learners, setLearners] = useState('40');
const [objective, setObjective] = useState('');
const [features, setFeatures] = useState<string[]>(['Mediasoup SFU', 'AI copilot', 'Whiteboard canvas']);
const [formError, setFormError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const selectedClassroom = useMemo(
() => classrooms.find((classroom) => classroom.id === selectedClassroomId) ?? classrooms[0],
[classrooms, selectedClassroomId]
);
const projectedCapacity = useMemo(() => getCapacityPlan(Number(learners) || 0), [learners]);
const selectedStorageDescription = storageDescriptions[storageProfile];
const enabledCount = useMemo(
() => [
classrooms.length >= 2,
featureOptions.length >= 10,
Boolean(selectedClassroom),
storageProfile === 'Hybrid mirrored backup',
].filter(Boolean).length,
[classrooms.length, selectedClassroom, storageProfile]
);
const toggleFeature = (feature: string) => {
setFeatures((currentFeatures) =>
currentFeatures.includes(feature)
? currentFeatures.filter((item) => item !== feature)
: [...currentFeatures, feature]
);
};
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
const resetForm = () => {
setName('');
setSupervisor('Majed Kaid Alsharabi');
setMode('Live Classroom');
setLanguageMode('Arabic + English');
setStorageProfile('Hybrid mirrored backup');
setLearners('40');
setObjective('');
setFeatures(['Mediasoup SFU', 'AI copilot', 'Whiteboard canvas']);
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setFormError('');
setSuccessMessage('');
const trimmedName = name.trim();
const trimmedSupervisor = supervisor.trim();
const trimmedObjective = objective.trim();
const learnerCount = Number(learners);
if (trimmedName.length < 3) {
setFormError('Give the classroom a clear name with at least 3 characters.');
return;
}
if (trimmedSupervisor.length < 2) {
setFormError('Add the responsible supervisor or teacher for this classroom.');
return;
}
if (!Number.isInteger(learnerCount) || learnerCount < 2 || learnerCount > 1000) {
setFormError('Expected learners must be a whole number from 2 to 1000.');
return;
}
if (trimmedObjective.length < 16) {
setFormError('Describe the class objective in at least 16 characters.');
return;
}
if (features.length < 2) {
setFormError('Select at least two BritishCE44 classroom capabilities.');
return;
}
const baseId = buildClassroomId(trimmedName);
const id = classrooms.some((classroom) => classroom.id === baseId)
? `${baseId}-${classrooms.length + 1}`
: baseId;
const createdClassroom: ClassroomPlan = {
id,
name: trimmedName,
supervisor: trimmedSupervisor,
mode,
languageMode,
storageProfile,
learners: learnerCount,
objective: trimmedObjective,
features,
createdAt: 'Just now',
};
setClassrooms((currentClassrooms) => [createdClassroom, ...currentClassrooms]);
setSelectedClassroomId(id);
setSuccessMessage(`${trimmedName} is planned as ${projectedCapacity.toLowerCase()} with ${storageProfile.toLowerCase()}.`);
resetForm();
};
const classroomUrl = selectedClassroom ? `https://localhost:3001/britishce44/classrooms/${selectedClassroom.id}` : '';
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<div className="min-h-screen bg-[#06111F] text-white">
<Head>
<title>{getPageTitle('Starter Page')}</title>
<title>{getPageTitle('BritishCE44 Enterprise Ultimate Platform')}</title>
<meta
name="description"
content="BritishCE44 Online School RFP interface for an AI-native hybrid classroom platform with WebRTC, LMS, Google Drive storage, and bilingual reporting."
/>
</Head>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your Autonomous Video Platform app!"/>
<div className="space-y-3">
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center '>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
</div>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
</BaseButtons>
</CardBox>
<header className="sticky top-0 z-30 border-b border-white/10 bg-[#06111F]/90 backdrop-blur-xl">
<div className="mx-auto flex max-w-7xl items-center justify-between px-5 py-4 lg:px-8">
<Link href="/" className="group inline-flex items-center gap-3 font-semibold tracking-tight">
<span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-gradient-to-br from-cyan-300 via-blue-500 to-violet-500 text-sm font-black text-slate-950 shadow-lg shadow-cyan-500/20">
CE44
</span>
<span>
<span className="block text-lg leading-none">BritishCE44</span>
<span className="block text-xs font-normal text-cyan-100/60">Enterprise Ultimate Platform</span>
</span>
</Link>
<nav className="hidden items-center gap-6 text-sm text-slate-200 md:flex">
<a className="hover:text-cyan-200" href="#suite">
Suite
</a>
<a className="hover:text-cyan-200" href="#planner">
Planner
</a>
<a className="hover:text-cyan-200" href="#architecture">
Architecture
</a>
<a className="hover:text-cyan-200" href="#classrooms">
Classrooms
</a>
</nav>
<BaseButton href="/login" label="Admin login" color="info" roundedFull className="shadow-lg shadow-cyan-500/20" />
</div>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
</header>
<main>
<section className="relative overflow-hidden">
<div className="absolute left-1/2 top-0 h-[42rem] w-[42rem] -translate-x-1/2 rounded-full bg-cyan-400/20 blur-3xl" />
<div className="absolute right-0 top-24 h-80 w-80 rounded-full bg-violet-500/20 blur-3xl" />
<div className="absolute bottom-0 left-10 h-72 w-72 rounded-full bg-emerald-400/10 blur-3xl" />
<div className="mx-auto grid max-w-7xl gap-10 px-5 py-16 lg:grid-cols-[1.05fr_0.95fr] lg:px-8 lg:py-24">
<div className="relative z-10 flex flex-col justify-center">
<div className="mb-6 inline-flex w-fit items-center gap-2 rounded-full border border-cyan-300/30 bg-cyan-300/10 px-4 py-2 text-sm text-cyan-100 shadow-2xl shadow-cyan-950/40">
<span className="h-2 w-2 rounded-full bg-emerald-300 shadow-[0_0_18px_rgba(110,231,183,0.9)]" />
RFP demo slice: AI-native online school control plane
</div>
<h1 className="max-w-4xl text-5xl font-black tracking-tight text-white md:text-7xl">
BritishCE44 Online School for secure, bilingual, AI-powered classrooms.
</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-slate-300">
A public product surface for the requested BritishCE44 Enterprise Ultimate Platform: 40+ digital
classrooms, WebRTC collaboration, AI proctoring, LMS workflows, parent reporting, and a hybrid
localhost plus Google Drive architecture.
</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<a
href="#planner"
className="inline-flex items-center justify-center rounded-full bg-cyan-300 px-6 py-3 font-semibold text-slate-950 shadow-xl shadow-cyan-500/20 transition hover:bg-cyan-200 focus:outline-none focus:ring-2 focus:ring-cyan-100"
>
Plan a classroom
</a>
<a
href="#architecture"
className="inline-flex items-center justify-center rounded-full border border-white/15 bg-white/10 px-6 py-3 font-semibold text-white transition hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-cyan-100"
>
View hybrid architecture
</a>
</div>
<dl className="mt-10 grid max-w-2xl grid-cols-2 gap-3 text-center md:grid-cols-4">
{[
['40+', 'classrooms'],
['4K', 'WebRTC target'],
['AR/EN', 'reporting'],
['3', 'delivery tracks'],
].map(([value, label]) => (
<div key={label} className="rounded-3xl border border-white/10 bg-white/[0.06] p-4 backdrop-blur">
<dt className="text-2xl font-black text-cyan-100">{value}</dt>
<dd className="mt-1 text-xs uppercase tracking-[0.2em] text-slate-400">{label}</dd>
</div>
))}
</dl>
</div>
<div className="relative z-10 rounded-[2rem] border border-white/10 bg-white/[0.07] p-4 shadow-2xl shadow-black/30 backdrop-blur-2xl">
<div className="rounded-[1.5rem] bg-[#0B1728] p-4">
<div className="mb-4 flex items-center justify-between gap-4">
<div>
<p className="text-sm text-slate-400">Live classroom preview</p>
<h2 className="text-xl font-bold">{selectedClassroom?.name}</h2>
</div>
<span className="rounded-full bg-emerald-300/10 px-3 py-1 text-xs font-semibold text-emerald-200">
Supervisor-ready
</span>
</div>
<div className="grid grid-cols-2 gap-3">
{['Teacher video', 'Whiteboard', 'AI copilot', 'Learner pods'].map((tile, index) => (
<div
key={tile}
className={`min-h-32 rounded-3xl border border-white/10 p-4 ${
index === 0 ? 'bg-gradient-to-br from-cyan-400/30 to-blue-500/20' : 'bg-white/[0.06]'
}`}
>
<div className="flex h-full flex-col justify-between">
<span className="text-xs uppercase tracking-[0.18em] text-slate-300">{tile}</span>
<span className="text-3xl font-black text-white/80">0{index + 1}</span>
</div>
</div>
))}
</div>
<div className="mt-4 rounded-3xl border border-cyan-300/20 bg-cyan-300/10 p-4">
<p className="text-sm font-semibold text-cyan-100">Hybrid server contract</p>
<p className="mt-2 text-sm leading-6 text-slate-300">
Core API, WebRTC signaling, database, and real-time state stay on HTTPS localhost. Google Drive is
positioned as the consent-based vault for recordings, course materials, static assets, and encrypted backups.
</p>
</div>
<div className="mt-4 grid grid-cols-3 gap-3 text-center text-xs text-slate-300">
{deploymentTracks.map(([title]) => (
<div key={title} className="rounded-2xl border border-white/10 bg-slate-950/40 p-3">
{title}
</div>
))}
</div>
</div>
</div>
</div>
</section>
<section id="suite" className="mx-auto max-w-7xl px-5 py-8 lg:px-8">
<div className="mb-6 max-w-3xl">
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-cyan-200">Education suite</p>
<h2 className="mt-3 text-4xl font-black tracking-tight md:text-5xl">A Teams, Zoom, Meet, and Jitsi challenger for schools.</h2>
<p className="mt-4 text-lg leading-8 text-slate-300">
This interface turns the RFP into a visible roadmap and demo workflow while leaving safe extension points
for Mediasoup, Redis, PostgreSQL, packaging, Google Drive OAuth, and AI services.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{suiteCards.map(([title, copy]) => (
<article key={title} className="rounded-[1.75rem] border border-white/10 bg-white/[0.06] p-6 shadow-xl shadow-black/10">
<div className="mb-5 h-12 w-12 rounded-2xl bg-gradient-to-br from-cyan-300 to-violet-500" />
<h3 className="text-xl font-bold">{title}</h3>
<p className="mt-3 leading-7 text-slate-300">{copy}</p>
</article>
))}
</div>
</section>
<section id="planner" className="mx-auto grid max-w-7xl gap-6 px-5 py-12 lg:grid-cols-[0.95fr_1.05fr] lg:px-8">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-cyan-200">Classroom planner</p>
<h2 className="mt-3 text-4xl font-black tracking-tight md:text-5xl">Configure a BritishCE44 digital classroom.</h2>
<p className="mt-4 text-lg leading-8 text-slate-300">
Choose the room type, bilingual mode, WebRTC/AI features, learner capacity, and the preferred hybrid
storage profile. The planner validates inputs and adds the classroom to the local demo registry.
</p>
<div className="mt-6 rounded-3xl border border-white/10 bg-white/[0.06] p-5">
<p className="text-sm font-semibold text-slate-200">Selected storage profile</p>
<p className="mt-2 text-sm leading-6 text-slate-300">{selectedStorageDescription}</p>
<p className="mt-4 text-sm text-cyan-100">Projected media plan: {projectedCapacity}</p>
</div>
{successMessage ? (
<div className="mt-6 rounded-3xl border border-emerald-300/30 bg-emerald-300/10 p-5 text-emerald-100" role="status">
<p className="font-semibold">Classroom plan created</p>
<p className="mt-1 text-sm text-emerald-100/80">{successMessage}</p>
</div>
) : null}
</div>
<form onSubmit={handleSubmit} className="rounded-[2rem] border border-white/10 bg-white/[0.08] p-5 shadow-2xl shadow-black/20 backdrop-blur-xl md:p-7">
{formError ? (
<div className="mb-5 rounded-2xl border border-rose-300/30 bg-rose-400/10 p-4 text-sm text-rose-100" role="alert">
{formError}
</div>
) : null}
<div className="grid gap-4 md:grid-cols-2">
<label className="block">
<span className="text-sm font-semibold text-slate-200">Classroom name</span>
<input
value={name}
onChange={(event) => setName(event.target.value)}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
placeholder="CE44 Science Room 12"
/>
</label>
<label className="block">
<span className="text-sm font-semibold text-slate-200">Supervisor / teacher</span>
<input
value={supervisor}
onChange={(event) => setSupervisor(event.target.value)}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
placeholder="Majed Kaid Alsharabi"
/>
</label>
<label className="block">
<span className="text-sm font-semibold text-slate-200">Classroom mode</span>
<select
value={mode}
onChange={(event) => setMode(event.target.value as ClassroomMode)}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
>
<option>Live Classroom</option>
<option>AI Exam Hall</option>
<option>Parent Conference</option>
<option>Teacher Studio</option>
</select>
</label>
<label className="block">
<span className="text-sm font-semibold text-slate-200">Language mode</span>
<select
value={languageMode}
onChange={(event) => setLanguageMode(event.target.value as LanguageMode)}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
>
<option>Arabic + English</option>
<option>English only</option>
<option>Arabic only</option>
</select>
</label>
<label className="block">
<span className="text-sm font-semibold text-slate-200">Expected learners</span>
<input
value={learners}
onChange={(event) => setLearners(event.target.value)}
inputMode="numeric"
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
placeholder="40"
/>
</label>
<label className="block">
<span className="text-sm font-semibold text-slate-200">Storage / backup profile</span>
<select
value={storageProfile}
onChange={(event) => setStorageProfile(event.target.value as StorageProfile)}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
>
<option>Hybrid mirrored backup</option>
<option>Google Drive vault</option>
<option>Local encrypted archive</option>
</select>
</label>
</div>
<label className="mt-4 block">
<span className="text-sm font-semibold text-slate-200">Learning objective</span>
<textarea
value={objective}
onChange={(event) => setObjective(event.target.value)}
rows={4}
className="mt-2 w-full rounded-2xl border border-white/10 bg-slate-950/60 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-200 focus:ring-2 focus:ring-cyan-200/30"
placeholder="Describe the lesson, assessment, or parent meeting outcome."
/>
</label>
<fieldset className="mt-5">
<legend className="text-sm font-semibold text-slate-200">Enabled classroom capabilities</legend>
<div className="mt-3 flex flex-wrap gap-2">
{featureOptions.map((feature) => {
const active = features.includes(feature);
return (
<button
key={feature}
type="button"
onClick={() => toggleFeature(feature)}
className={`rounded-full border px-4 py-2 text-sm transition focus:outline-none focus:ring-2 focus:ring-cyan-100 ${
active
? 'border-cyan-200 bg-cyan-200 text-slate-950'
: 'border-white/10 bg-slate-950/40 text-slate-300 hover:bg-white/10'
}`}
>
{feature}
</button>
);
})}
</div>
</fieldset>
<div className="mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-slate-400">No credentials are exposed here; admin password rotation belongs in the protected setup flow.</p>
<button
type="submit"
className="rounded-full bg-emerald-300 px-6 py-3 font-semibold text-slate-950 shadow-xl shadow-emerald-500/20 transition hover:bg-emerald-200 focus:outline-none focus:ring-2 focus:ring-emerald-100"
>
Create classroom plan
</button>
</div>
</form>
</section>
<section id="architecture" className="mx-auto max-w-7xl px-5 py-12 lg:px-8">
<div className="rounded-[2rem] border border-white/10 bg-white/[0.07] p-5 shadow-2xl shadow-black/20 md:p-8">
<div className="grid gap-8 lg:grid-cols-[0.85fr_1.15fr]">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-cyan-200">Hybrid architecture</p>
<h2 className="mt-3 text-4xl font-black tracking-tight">Local real-time core plus Google Drive vault.</h2>
<p className="mt-4 leading-8 text-slate-300">
The page now presents the requested deployment model without pretending the full installer stack is already complete.
Backend WebRTC, Redis, PostgreSQL, Google Drive OAuth, EXE packaging, and APK packaging remain explicit implementation tracks.
</p>
</div>
<div className="grid gap-3 md:grid-cols-2">
{[
['HTTPS localhost', 'Core services, WebRTC signaling, API, and the future Mediasoup control plane.'],
['PostgreSQL + Redis', 'Local durable school data, real-time state, meetings, rooms, roles, and event recovery.'],
['Google Drive', 'Consent-based cloud vault for recordings, uploads, course materials, backups, and static assets.'],
['Single consent setup', 'Installer goal: request camera, microphone, and Drive OAuth once, then run without manual npm or env steps.'],
].map(([title, copy]) => (
<article key={title} className="rounded-3xl border border-white/10 bg-slate-950/35 p-5">
<p className="font-bold text-white">{title}</p>
<p className="mt-2 text-sm leading-6 text-slate-300">{copy}</p>
</article>
))}
</div>
</div>
</div>
</section>
<section id="classrooms" className="mx-auto grid max-w-7xl gap-6 px-5 py-12 lg:grid-cols-[0.82fr_1.18fr] lg:px-8">
<div className="rounded-[2rem] border border-white/10 bg-white/[0.08] p-5 shadow-2xl shadow-black/20 md:p-7">
<div className="mb-5 flex items-start justify-between gap-4">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-cyan-200">Classroom registry</p>
<h2 className="mt-1 text-2xl font-black">{classrooms.length} planned spaces</h2>
</div>
<span className="rounded-full bg-white/10 px-3 py-1 text-xs text-slate-300">Local demo state</span>
</div>
<div className="space-y-3">
{classrooms.map((classroom) => {
const active = classroom.id === selectedClassroom?.id;
return (
<button
key={classroom.id}
type="button"
onClick={() => setSelectedClassroomId(classroom.id)}
className={`w-full rounded-3xl border p-4 text-left transition focus:outline-none focus:ring-2 focus:ring-cyan-100 ${
active ? 'border-cyan-200 bg-cyan-200/10' : 'border-white/10 bg-slate-950/30 hover:bg-white/10'
}`}
>
<div className="flex items-start justify-between gap-4">
<div>
<p className="font-bold text-white">{classroom.name}</p>
<p className="mt-1 text-sm text-slate-400">
{classroom.mode} · {classroom.learners} learners · {classroom.createdAt}
</p>
</div>
<span className={`h-10 w-10 shrink-0 rounded-2xl bg-gradient-to-br ${modeAccent[classroom.mode]}`} />
</div>
</button>
);
})}
</div>
</div>
{selectedClassroom ? (
<article className="rounded-[2rem] border border-white/10 bg-white/[0.08] p-5 shadow-2xl shadow-black/20 md:p-7">
<div className="flex flex-col gap-5 md:flex-row md:items-start md:justify-between">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-cyan-200">Classroom detail</p>
<h2 className="mt-2 text-4xl font-black tracking-tight">{selectedClassroom.name}</h2>
<p className="mt-3 max-w-2xl leading-7 text-slate-300">{selectedClassroom.objective}</p>
</div>
<span className={`h-16 w-16 rounded-3xl bg-gradient-to-br ${modeAccent[selectedClassroom.mode]} shadow-xl shadow-cyan-500/20`} />
</div>
<div className="mt-6 grid gap-3 md:grid-cols-3">
{[
['Supervisor', selectedClassroom.supervisor],
['Language', selectedClassroom.languageMode],
['Capacity plan', getCapacityPlan(selectedClassroom.learners)],
['Mode', selectedClassroom.mode],
['Storage', selectedClassroom.storageProfile],
['Readiness', `${enabledCount}/4 demo checks`],
].map(([label, value]) => (
<div key={label} className="rounded-3xl border border-white/10 bg-slate-950/35 p-4">
<p className="text-xs uppercase tracking-[0.2em] text-slate-500">{label}</p>
<p className="mt-2 font-bold text-white">{value}</p>
</div>
))}
</div>
<div className="mt-6 rounded-3xl border border-white/10 bg-slate-950/35 p-4">
<p className="text-sm font-semibold text-slate-200">Local classroom endpoint preview</p>
<div className="mt-3 flex flex-col gap-3 rounded-2xl border border-white/10 bg-black/30 p-3 sm:flex-row sm:items-center sm:justify-between">
<code className="overflow-x-auto text-sm text-cyan-100">{classroomUrl}</code>
<button
type="button"
onClick={() => setSuccessMessage(`Classroom handoff prepared for ${selectedClassroom.name}.`)}
className="rounded-full border border-cyan-200/40 px-4 py-2 text-sm font-semibold text-cyan-100 transition hover:bg-cyan-200/10 focus:outline-none focus:ring-2 focus:ring-cyan-100"
>
Mark ready for admin review
</button>
</div>
</div>
<div className="mt-6">
<p className="mb-3 text-sm font-semibold text-slate-200">Enabled capabilities</p>
<div className="flex flex-wrap gap-2">
{selectedClassroom.features.map((feature) => (
<span key={feature} className="rounded-full bg-white/10 px-4 py-2 text-sm text-slate-200">
{feature}
</span>
))}
</div>
</div>
</article>
) : null}
</section>
<section className="mx-auto max-w-7xl px-5 pb-14 lg:px-8">
<div className="grid gap-4 md:grid-cols-3">
{deploymentTracks.map(([title, copy]) => (
<article key={title} className="rounded-[1.75rem] border border-white/10 bg-white/[0.06] p-6">
<p className="text-sm font-semibold uppercase tracking-[0.22em] text-cyan-200">Delivery track</p>
<h3 className="mt-2 text-2xl font-black">{title}</h3>
<p className="mt-3 leading-7 text-slate-300">{copy}</p>
</article>
))}
</div>
</section>
</main>
<footer className="border-t border-white/10 bg-black/20">
<div className="mx-auto flex max-w-7xl flex-col gap-3 px-5 py-8 text-sm text-slate-400 md:flex-row md:items-center md:justify-between lg:px-8">
<p>© 2026 BritishCE44 Online School. Public RFP demo surface for the enterprise classroom platform.</p>
<div className="flex flex-wrap gap-4">
<Link className="hover:text-cyan-100" href="/privacy-policy/">
Privacy Policy
</Link>
<Link className="hover:text-cyan-100" href="/terms-of-use/">
Terms
</Link>
<Link className="hover:text-cyan-100" href="/login">
Admin Login
</Link>
</div>
</div>
</footer>
</div>
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
BritishCE44Platform.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,9 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
import { useAppDispatch } from '../stores/hooks';
import { useAppSelector } from '../stores/hooks';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';