Team Ai
Apppublic

nmhbv/anycoder-48fe0aba

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
app.py795 linesDownload Raw Back to root
1I’m improving your React application and converting it to a modern Next.js + Tailwind setup with an integrated content management panel and API routes. Below are all the necessary files for a clean, production-ready setup.2 3=== Dockerfile ===4FROM node:18-alpine AS deps5WORKDIR /app6COPY package.json package-lock.json* ./7RUN npm ci8 9FROM node:18-alpine AS builder10WORKDIR /app11COPY --from=deps /app/node_modules ./node_modules12COPY . .13RUN npm run build14 15FROM node:18-alpine AS runner16WORKDIR /app17ENV NODE_ENV=production18RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs19 20COPY --from=builder /app/public ./public21COPY --from=builder /app/.next/standalone ./22COPY --from=builder /app/.next/static ./.next/static23 24USER nextjs25EXPOSE 300026ENV PORT 300027CMD ["node", "server.js"]28 29=== package.json ===30{31  "name": "alandalus-institute",32  "version": "1.0.0",33  "private": true,34  "scripts": {35    "dev": "next dev -p 3000",36    "build": "next build",37    "start": "next start -p 3000"38  },39  "dependencies": {40    "cookie": "^0.6.0",41    "next": "14.2.4",42    "react": "18.3.1",43    "react-dom": "18.3.1"44  },45  "devDependencies": {46    "autoprefixer": "^10.4.20",47    "postcss": "^8.4.41",48    "tailwindcss": "^3.4.10"49  }50}51 52=== next.config.js ===53/** @type {import('next').NextConfig} */54const nextConfig = {55  reactStrictMode: true,56  output: 'standalone',57  images: {58    unoptimized: true59  }60};61 62module.exports = nextConfig;63 64=== tailwind.config.js ===65/** @type {import('tailwindcss').Config} */66module.exports = {67  content: [68    "./pages/**/*.{js,jsx}",69    "./components/**/*.{js,jsx}",70    "./lib/**/*.{js,jsx}"71  ],72  theme: {73    extend: {74      fontFamily: {75        'arabic': ['Noto Sans Arabic', 'system-ui', 'sans-serif'],76        'serif': ['Lora', 'ui-serif', 'serif'],77        'display': ['Cinzel', 'Lora', 'serif']78      },79      colors: {80        brand: {81          crimson: '#8f1d21',82          gold: '#c6a44d',83          teal: '#0f4c5c',84          umber: '#6b3e2e',85          olive: '#6b8a3a',86          sky: '#5d8aa8',87          sand: '#d8c89f',88          ink: '#2b241e',89          inkSoft: '#4a4138',90          base: '#f4ead1',91          paper: '#fcf6e6'92        }93      },94      boxShadow: {95        'paper': '0 12px 40px rgba(27,26,23,.22)'96      },97      borderRadius: {98        'xl': '1rem'99      }100    }101  },102  plugins: []103};104 105=== postcss.config.js ===106module.exports = {107  plugins: {108    tailwindcss: {},109    autoprefixer: {}110  }111};112 113=== styles/globals.css ===114@tailwind base;115@tailwind components;116@tailwind utilities;117 118@layer base {119  html {120    scroll-behavior: smooth;121  }122  body {123    @apply font-arabic text-brand-ink bg-gradient-to-br from-brand-sand/40 via-brand-base to-brand-sand/70;124    -webkit-font-smoothing: antialiased;125    -moz-osx-font-smoothing: grayscale;126  }127}128 129@layer components {130  .skip-link {131    @apply absolute -top-10 left-4 bg-brand-crimson text-white px-3 py-2 rounded-lg z-50 focus:top-4;132  }133  .container {134    @apply max-w-6xl mx-auto;135  }136  .section {137    @apply py-16 px-4 md:py-20;138  }139  .section-title {140    @apply text-3xl md:text-4xl font-display text-brand-ink mb-4;141  }142  .section-subtitle {143    @apply text-brand-inkSoft max-w-3xl mb-8;144  }145  .paper-card {146    @apply relative bg-brand-paper border border-brand-gold/20 rounded-xl shadow-paper overflow-hidden transition-transform duration-200 hover:-translate-y-1 hover:shadow-paper;147  }148  .paper-card::before {149    content: "";150    @apply absolute -right-6 -top-6 w-24 h-24 opacity-40 pointer-events-none;151    background:152      radial-gradient(closest-side, rgba(111,89,54,0.2), transparent 70%),153      conic-gradient(from 0deg, rgba(143,29,33,0.35) 0 25%, transparent 25% 50%, rgba(198,164,77,0.35) 50% 75%, transparent 75% 100%);154    mask: radial-gradient(closest-side, black 70%, transparent 72%);155    transform: rotate(25deg);156  }157  .btn {158    @apply inline-flex items-center gap-2 border border-black/20 px-4 py-2 rounded-xl font-semibold shadow-sm transition-all duration-150 hover:-translate-y-0.5;159  }160  .btn-primary {161    @apply bg-brand-crimson text-white shadow-brand-crimson/30;162  }163  .btn-ghost {164    @apply bg-white/60 text-brand-ink shadow-none hover:bg-white/80;165  }166  .tag {167    @apply inline-flex items-center gap-2 text-sm px-3 py-1 rounded-full border border-dashed border-brand-gold/40 bg-white/60 text-brand-umber;168  }169  .ornament {170    @apply w-full h-10 opacity-70 transform rotate-180;171  }172  .callout {173    @apply grid grid-cols-[auto,1fr] gap-3 p-4 rounded-xl border border-brand-gold/20 bg-white/60 shadow;174  }175  .icon {176    @apply w-10 h-10 grid place-items-center rounded-lg bg-gradient-to-br from-white to-brand-gold/40 border border-brand-gold/40 text-brand-umber font-bold;177  }178  .admin-card {179    @apply bg-brand-paper border border-brand-gold/20 rounded-xl shadow-paper p-4;180  }181  .table {182    @apply w-full border-separate;183  }184  .row-card {185    @apply flex items-center gap-3 p-3 rounded-lg border border-brand-gold/20 bg-white/70;186  }187}188 189=== lib/content.js ===190export function defaultContent() {191  const now = new Date().toISOString();192  return {193    about: {194      text: "تأسس على تلة俯瞰 المدينة القديمة، يمزج حرمنا بين أفنية وحدائق ومشربيات واستوديوهات مشرقة للاكتشاف. نلتزم بالعلوم الكلاسيكية والآداب الرفيعة والبحث المعاصر المنضبط بمبادئ أخلاقية وشعور بالجمال.",195      footerAbout: "موقع تجريبي للتطوير والاختبار.",196      cards: [197        {198          id: "card_1",199          title: "كلاسيكي ومعاصر",200          body: "من Arts الليبرالية إلى علم البيانات، يجمع منهجنا بين تفسير النصوص والمنطق والفلسفة والعلوم الطبيعية والحاسوبية.",201          tag: "منهج • توازن العقل والنقل"202        },203        {204          id: "card_2",205          title: "الفنون والهندسة",206          body: "ندرّب المهندسين والفنانين والمصممين على تقنيات ونظريات تمتد من أنماط الأفنية إلى التصميم الحاسوبي المعاصر.",207          tag: "استوديو • حرفة تلتقي بالكود"208        },209        {210          id: "card_3",211          title: "خدمة ومجتمع",212          body: "عيادات ومساعدة قانونية وميدان يتعاون مع المجتمعات المحلية لمعالجة قضايا العصر بتواضع ورعاية.",213          tag: "تشارك • علم في خدمة"214        }215      ]216    },217    news: [218      { id: "news_1", title: "افتتاح مركز الذكاء الاصطناعي الأخلاقي", body: "تم تدشين مختبر جديد للذكاء الاصطناعي المسؤول في حرم العلوم.", date: now, published: true },219      { id: "news_2", title: "ندوة الربيع للعلوم الإنسانية", body: "ندوة مفتوحة حول أخلاقيات البيانات والمجتمع.", date: now, published: true }220    ],221    contact: [222      { id: "contact_1", label: "البريد", value: "info@alandalus-institute.example", type: "email" },223      { id: "contact_2", label: "الهاتف", value: "+1 555‑0100", type: "phone" },224      { id: "contact_3", label: "العنوان", value: "شارع知识和智慧، المدينة القديمة", type: "text" }225    ],226    donation: [227      { id: "don_1", title: "منح دراسية", desc: "ادعم منح التفوق والاحتياجات", target: "annual", goal: 250000, raised: 82000, link: "#" },228      { id: "don_2", title: "مختبر الحاسوب", desc: "ترقية مختبرات علوم البيانات", target: "lab", goal: 180000, raised: 42000, link: "#" },229      { id: "don_3", title: "ترميم المكتبة", desc: "حفظ التراث وإعادة تأهيل قاعات القراءة", target: "library", goal: 320000, raised: 103000, link: "#" }230    ]231  };232}233 234=== lib/utils.js ===235export function uid(prefix = "id") {236  if (typeof crypto !== "undefined" && crypto.randomUUID) {237    return `${prefix}_${crypto.randomUUID()}`;238  }239  return `${prefix}_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;240}241 242export function formatDate(iso) {243  try {244    const d = new Date(iso);245    return new Intl.DateTimeFormat("ar-EG", { dateStyle: "full" }).format(d);246  } catch {247    return iso;248  }249}250 251=== components/Header.js ===252import { useState, useEffect } from "react";253 254export default function Header() {255  const [open, setOpen] = useState(false);256  const [adminMode, setAdminMode] = useState(false);257  const [hash, setHash] = useState("");258 259  useEffect(() => {260    const onHash = () => setHash(location.hash.replace("#", "") || "top");261    onHash();262    window.addEventListener("hashchange", onHash);263    return () => window.removeEventListener("hashchange", onHash);264  }, []);265 266  useEffect(() => {267    setAdminMode(document.body.dataset.adminOpen === "true");268  }, []);269 270  useEffect(() => {271    if (adminMode) {272      document.body.dataset.adminOpen = "true";273    } else {274      document.body.dataset.adminOpen = "false";275    }276  }, [adminMode]);277 278  return (279    <header className="sticky top-0 z-40 bg-white/80 backdrop-blur border-b border-brand-gold/20">280      <div className="container flex items-center justify-between py-3 px-4">281        <a className="flex items-center gap-3 text-brand-ink min-w-0" href="#top" aria-label="الصفحة الرئيسية">282          <div className="w-10 h-10 rounded-xl grid place-items-center font-display font-bold border-2 border-brand-gold/40 bg-gradient-to-br from-brand-gold/60 to-brand-gold text-brand-umber shadow-inner">283            AA284          </div>285          <div className="font-display font-bold text-lg md:text-xl">معهد الأندلس العالي</div>286        </a>287 288        <nav className="hidden md:flex items-center gap-6" aria-label="القائمة الرئيسية">289          <NavLink href="#about" active={hash === "about"}>عن الجامعة</NavLink>290          <NavLink href="#news" active={hash === "news"}>الأخبار</NavLink>291          <NavLink href="#contact" active={hash === "contact"}>تواصل معنا</NavLink>292          <NavLink href="#donate" active={hash === "donate"}>تبرع</NavLink>293        </nav>294 295        <div className="flex items-center gap-2">296          <button className="btn btn-ghost" onClick={() => setAdminMode(!adminMode)} aria-label="فتح نظام الإدارة">297            إدارة298          </button>299          <button300            className="md:hidden btn btn-ghost"301            onClick={() => setOpen(!open)}302            aria-expanded={open}303            aria-controls="mobileMenu"304            aria-label="فتح القائمة"305          >306            ☰ القائمة307          </button>308        </div>309      </div>310      {open && (311        <div id="mobileMenu" className="md:hidden border-t border-brand-gold/20 bg-white/90">312          <div className="container px-4 py-3 flex flex-col gap-2">313            <MobileNav href="#about">عن الجامعة</MobileNav>314            <MobileNav href="#news">الأخبار</MobileNav>315            <MobileNav href="#contact">تواصل معنا</MobileNav>316            <MobileNav href="#donate">تبرع</MobileNav>317          </div>318        </div>319      )}320    </header>321  );322}323 324function NavLink({ href, children, active }) {325  return (326    <a327      href={href}328      className={`px-3 py-2 rounded-lg font-medium transition-colors ${active ? "bg-brand-gold/20 underline underline-offset-4" : "text-brand-inkSoft hover:text-brand-ink hover:bg-brand-gold/10"}`}329    >330      {children}331    </a>332  );333}334 335function MobileNav({ href, children }) {336  return (337    <a href={href} className="px-3 py-2 rounded-lg text-brand-inkSoft hover:text-brand-ink hover:bg-brand-gold/10">338      {children}339    </a>340  );341}342 343=== components/Footer.js ===344export default function Footer() {345  return (346    <footer className="border-t border-brand-gold/20 bg-white/90 mt-16">347      <div className="container grid gap-8 md:grid-cols-3 py-8 px-4">348        <div>349          <h4 className="font-display text-brand-ink text-xl mb-2">معهد الأندلس العالي</h4>350          <p id="footerAbout" className="text-brand-inkSoft">351            {/* Filled by page */}352          </p>353          <p className="text-xs text-brand-inkSoft mt-2">354            مبني بـ{" "}355            <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener" className="underline hover:text-brand-ink">356              anycoder357            </a>358          </p>359        </div>360        <div>361          <h4 className="font-display text-brand-ink text-xl mb-2">روابط سريعة</h4>362          <ul className="space-y-1">363            <li><a href="#about" className="text-brand-inkSoft hover:text-brand-ink">عن الجامعة</a></li>364            <li><a href="#news" className="text-brand-inkSoft hover:text-brand-ink">الأخبار</a></li>365            <li><a href="#contact" className="text-brand-inkSoft hover:text-brand-ink">تواصل معنا</a></li>366            <li><a href="#donate" className="text-brand-inkSoft hover:text-brand-ink">تبرع</a></li>367          </ul>368        </div>369        <div>370          <h4 className="font-display text-brand-ink text-xl mb-2">حقوق</h4>371          <p className="text-brand-inkSoft">© <span id="year">{new Date().getFullYear()}</span> معهد الأندلس العالي</p>372        </div>373      </div>374      <Ornament />375    </footer>376  );377}378 379function Ornament() {380  return (381    <svg className="ornament" viewBox="0 0 1200 60" role="img" aria-hidden="true">382      <defs>383        <pattern id="ornament-border" patternUnits="userSpaceOnUse" width="120" height="40">384          <rect width="120" height="40" fill="none"></rect>385          <g stroke="#7c5c2e" strokeWidth="1.2" opacity="0.7">386            <path d="M10,20 Q20,2 30,20 T50,20 T70,20 T90,20 T110,20"></path>387            <path d="M10,22 Q20,38 30,22 T50,22 T70,22 T90,22 T110,22" opacity="0.6"></path>388          </g>389          <g fill="#b9923c" opacity="0.8">390            <polygon points="20,20 22,14 28,14 23.5,10 25.5,4 20,8 14.5,4 16.5,10 12,14 18,14"></polygon>391            <polygon points="60,20 62,14 68,14 63.5,10 65.5,4 60,8 54.5,4 56.5,10 52,14 58,14"></polygon>392            <polygon points="100,20 102,14 108,14 103.5,10 105.5,4 100,8 94.5,4 96.5,10 92,14 98,14"></polygon>393          </g>394        </pattern>395      </defs>396      <rect x="0" y="0" width="1200" height="60" fill="url(#ornament-border)"></rect>397    </svg>398  );399}400 401=== components/Hero.js ===402import { useEffect, useState } from "react";403 404export default function Hero() {405  const [hijri, setHijri] = useState("جارٍ تحميل التقويم…");406  const [gregorian, setGregorian] = useState("جارٍ تحميل التاريخ…");407 408  useEffect(() => {409    setGregorian(new Date().toLocaleDateString("ar-EG", { dateStyle: "full" }));410    setHijri(""); // You can integrate a Hijri API here if desired411  }, []);412 413  return (414    <section className="relative min-h-[72vh] grid place-items-center overflow-hidden border-b border-brand-gold/20">415      <div className="absolute inset-0 -z-10 bg-[radial-gradient(60%_50%_at_10%_20%,rgba(15,76,92,.25),transparent_60%),radial-gradient(60%_50%_at_90%_30%,rgba(143,29,33,.25),transparent_60%),linear-gradient(180deg,rgba(255,255,255,.75),rgba(255,255,255,.45))]" />416      <div417        className="absolute inset-0 opacity-30 mix-blend-multiply -z-10"418        style={{419          backgroundImage:420            "repeating-linear-gradient(45deg, transparent 0 22px, rgba(111,89,54,0.18) 22px 24px), repeating-linear-gradient(-45deg, transparent 0 22px, rgba(111,89,54,0.18) 22px 24px)",421          animation: "driftPattern 35s linear infinite",422          maskImage: "radial-gradient(75% 55% at 50% 35%, #000 60%, transparent 85%)"423        }}424      />425      <div className="container grid gap-4 px-4">426        <div className="flex flex-wrap gap-2">427          <span className="chip">{hijri}</span>428          <span className="chip">{gregorian}</span>429        </div>430        <h1 className="font-display text-4xl md:text-6xl text-brand-ink leading-tight">431          معهد الأندلس العالي432        </h1>433        <p className="text-brand-inkSoft max-w-3xl">434          حيث يلتقي التراث بالحداثة. مجتمع学者ين وطلاب يبني مستقبلاً أكثر أخلاقاً وجمالاً.435        </p>436        <div className="flex flex-wrap gap-3">437          <a href="#about" className="btn btn-primary">عن الجامعة</a>438          <a href="#donate" className="btn btn-ghost">تبرع الآن</a>439        </div>440        <Ornament />441      </div>442    </section>443  );444}445 446function Ornament() {447  return (448    <svg className="ornament" viewBox="0 0 1200 60" aria-hidden="true">449      <defs>450        <pattern id="ornament-border-hero" patternUnits="userSpaceOnUse" width="120" height="40">451          <rect width="120" height="40" fill="none"></rect>452          <g stroke="#7c5c2e" strokeWidth="1.2" opacity="0.7">453            <path d="M10,20 Q20,2 30,20 T50,20 T70,20 T90,20 T110,20"></path>454            <path d="M10,22 Q20,38 30,22 T50,22 T70,22 T90,22 T110,22" opacity="0.6"></path>455          </g>456          <g fill="#b9923c" opacity="0.8">457            <polygon points="20,20 22,14 28,14 23.5,10 25.5,4 20,8 14.5,4 16.5,10 12,14 18,14"></polygon>458            <polygon points="60,20 62,14 68,14 63.5,10 65.5,4 60,8 54.5,4 56.5,10 52,14 58,14"></polygon>459            <polygon points="100,20 102,14 108,14 103.5,10 105.5,4 100,8 94.5,4 96.5,10 92,14 98,14"></polygon>460          </g>461        </pattern>462      </defs>463      <rect x="0" y="0" width="1200" height="60" fill="url(#ornament-border-hero)"></rect>464    </svg>465  );466}467 468function Chip({ children }) {469  return <span className="px-3 py-1 rounded-full bg-white/70 border border-brand-gold/40 text-brand-umber text-sm">{children}</span>;470}471 472=== components/AdminPanel.js ===473import { useEffect, useMemo, useState } from "react";474import { uid } from "../lib/utils";475import { formatDate } from "../lib/utils";476 477export default function AdminPanel() {478  const [loggedIn, setLoggedIn] = useState(false);479  const [checking, setChecking] = useState(true);480  const [tab, setTab] = useState("about");481  const [envInfo, setEnvInfo] = useState("محلي");482  const [status, setStatus] = useState({}); // status for CRUD483 484  const [about, setAbout] = useState(null);485  const [news, setNews] = useState([]);486  const [contact, setContact] = useState([]);487  const [donation, setDonation] = useState([]);488 489  // seed status490  const [lastBackup, setLastBackup] = useState("");491 492  const load = async () => {493    setChecking(true);494    const res = await fetch("/api/content");495    const data = await res.json();496    setAbout(data.about);497    setNews(data.news || []);498    setContact(data.contact || []);499    setDonation(data.donation || []);500    setLastBackup(data.lastBackup ? formatDate(data.lastBackup) : "لم تُسجل نسخ بعد");501    setChecking(false);502  };503 504  useEffect(() => {505    load();506  }, []);507 508  useEffect(() => {509    // check login510    fetch("/api/admin/status").then(r => r.json()).then(d => {511      setLoggedIn(d.authenticated);512      setEnvInfo(d.env || "محلي");513      setChecking(false);514    });515  }, []);516 517  const onLogin = async (user, pass) => {518    setChecking(true);519    const res = await fetch("/api/admin/login", {520      method: "POST",521      headers: { "content-type": "application/json" },522      body: JSON.stringify({ username: user, password: pass })523    });524    const ok = res.ok;525    if (ok) await load();526    setLoggedIn(ok);527    setChecking(false);528  };529 530  const onLogout = async () => {531    await fetch("/api/admin/logout", { method: "POST" });532    setLoggedIn(false);533  };534 535  const onSaveAbout = async () => {536    setStatus(s => ({ ...s, about: "saving" }));537    const res = await fetch("/api/content/about", {538      method: "PUT",539      headers: { "content-type": "application/json" },540      body: JSON.stringify(about)541    });542    setStatus(s => ({ ...s, about: res.ok ? "saved" : "error" }));543    if (res.ok) await load();544    setTimeout(() => setStatus(s => ({ ...s, about: "" })), 1500);545  };546 547  const addAboutCard = () => {548    setAbout(a => ({ ...a, cards: [...a.cards, { id: uid("card"), title: "", body: "", tag: "" }] }));549  };550 551  const addNews = async () => {552    const item = { id: uid("news"), title: "", body: "", date: new Date().toISOString(), published: true };553    const res = await fetch("/api/content/news", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(item) });554    if (res.ok) await load();555  };556 557  const addContact = async () => {558    const item = { id: uid("contact"), label: "", value: "", type: "text" };559    const res = await fetch("/api/content/contact", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(item) });560    if (res.ok) await load();561  };562 563  const addDonation = async () => {564    const item = { id: uid("don"), title: "", desc: "", target: "general", goal: 0, raised: 0, link: "#" };565    const res = await fetch("/api/content/donation", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(item) });566    if (res.ok) await load();567  };568 569  const updateSection = async (section, item) => {570    const res = await fetch(`/api/content/${section}/${item.id}`, {571      method: "PUT",572      headers: { "content-type": "application/json" },573      body: JSON.stringify(item)574    });575    if (res.ok) await load();576  };577 578  const deleteItem = async (section, id) => {579    const res = await fetch(`/api/content/${section}/${id}`, { method: "DELETE" });580    if (res.ok) await load();581  };582 583  const exportData = async () => {584    const res = await fetch("/api/content");585    const data = await res.json();586    const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });587    const url = URL.createObjectURL(blob);588    const a = document.createElement("a");589    a.href = url;590    a.download = `alandalus-backup-${new Date().toISOString().slice(0,10)}.json`;591    a.click();592    URL.revokeObjectURL(url);593  };594 595  const importData = async (file) => {596    const text = await file.text();597    const json = JSON.parse(text);598    await fetch("/api/content", {599      method: "PUT",600      headers: { "content-type": "application/json" },601      body: JSON.stringify(json)602    });603    await load();604  };605 606  const resetDemo = async () => {607    await fetch("/api/content/reset", { method: "POST" });608    await load();609  };610 611  if (checking) {612    return (613      <div className="fixed inset-0 z-50 bg-white/70 backdrop-blur flex items-center justify-center">614        <div className="admin-card">جارٍ التحميل…</div>615      </div>616    );617  }618 619  return (620    <div className="fixed inset-0 z-40 overflow-y-auto bg-white/90 backdrop-blur border-t border-brand-gold/20">621      <div className="sticky top-0 bg-white/95 border-b border-brand-gold/20 px-4 py-3 flex items-center justify-between">622        <div className="flex items-center gap-3">623          <strong className="font-display text-brand-ink">لوحة إدارة المحتوى</strong>624          <span className="px-2 py-1 rounded-full border border-brand-gold/40 text-brand-umber text-xs">{envInfo}</span>625          <span className="px-2 py-1 rounded-full border border-brand-gold/40 text-brand-umber text-xs">626            {loggedIn ? "مسجل الدخول" : "غير مسجل الدخول"}627          </span>628          <span className="px-2 py-1 rounded-full border border-brand-gold/40 text-brand-umber text-xs">629            آخر نسخة: {lastBackup}630          </span>631        </div>632        <div className="flex items-center gap-2">633          <button className="btn btn-ghost" onClick={exportData}>تصدير</button>634          <label className="btn btn-ghost" style={{ cursor: "pointer" }}>635            استيراد636            <input type="file" className="hidden" accept="application/json" onChange={(e) => importData(e.target.files[0])} />637          </label>638          <button className="btn btn-ghost bg-red-50 text-red-700 border-red-200" onClick={resetDemo}>إعادة تعيين</button>639          {loggedIn ? (640            <button className="btn bg-brand-ink text-white" onClick={onLogout}>قفل</button>641          ) : null}642          <a className="btn btn-ghost" href="#top">خروج</a>643        </div>644      </div>645 646      {!loggedIn ? (647        <div className="container px-4 py-6">648          <LoginCard onLogin={onLogin} />649        </div>650      ) : (651        <div className="container px-4 py-6 space-y-6">652          <div className="flex flex-wrap gap-2">653            <button className={`btn ${tab === "about" ? "btn-primary" : "btn-ghost"}`} onClick={() => setTab("about")}>عن الجامعة</button>654            <button className={`btn ${tab === "news" ? "btn-primary" : "btn-ghost"}`} onClick={() => setTab("news")}>الأخبار</button>655            <button className={`btn ${tab === "contact" ? "btn-primary" : "btn-ghost"}`} onClick={() => setTab("contact")}>تواصل معنا</button>656            <button className={`btn ${tab === "donation" ? "btn-primary" : "btn-ghost"}`} onClick={() => setTab("donation")}>التبرع</button>657          </div>658 659          {tab === "about" && about && (660            <section className="admin-card">661              <div className="flex items-center justify-between mb-3">662                <h3 className="font-display text-brand-ink">إدارة: عن الجامعة</h3>663                <button className="btn btn-ghost" onClick={onSaveAbout}>664                  حفظ665                  {status.about === "saving" && <span className="text-xs">…</span>}666                  {status.about === "saved" && <span className="text-xs text-green-700">تم</span>}667                  {status.about === "error" && <span className="text-xs text-red-700">خطأ</span>}668                </button>669              </div>670              <div className="grid md:grid-cols-2 gap-6">671                <div className="space-y-3">672                  <div>673                    <label className="block font-semibold text-brand-inkSoft mb-1">النص التعريفي</label>674                    <textarea675                      className="w-full p-3 rounded-lg border border-brand-gold/40 bg-white/80"676                      rows={6}677                      value={about.text}678                      onChange={e => setAbout({ ...about, text: e.target.value })}679                    />680                  </div>681                  <div>682                    <label className="block font-semibold text-brand-inkSoft mb-1">وصف الفوتر</label>683                    <textarea684                      className="w-full p-3 rounded-lg border border-brand-gold/40 bg-white/80"685                      rows={3}686                      value={about.footerAbout}687                      onChange={e => setAbout({ ...about, footerAbout: e.target.value })}688                    />689                  </div>690                </div>691                <div>692                  <div className="flex items-center justify-between mb-2">693                    <h4 className="font-display text-brand-ink">البطاقات</h4>694                    <button className="btn btn-ghost" onClick={addAboutCard}>إضافة بطاقة</button>695                  </div>696                  <div className="space-y-3">697                    {about.cards.map((c, i) => (698                      <div key={c.id} className="admin-card">699                        <div className="grid md:grid-cols-3 gap-3">700                          <div>701                            <label className="block text-sm text-brand-inkSoft mb-1">العنوان</label>702                            <input className="w-full p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={c.title} onChange={e => {703                              const val = e.target.value; setAbout(a => ({ ...a, cards: a.cards.map(x => x.id === c.id ? { ...x, title: val } : x) }));704                            }} />705                          </div>706                          <div className="md:col-span-2">707                            <label className="block text-sm text-brand-inkSoft mb-1">الوصف</label>708                            <textarea className="w-full p-2 rounded-lg border border-brand-gold/40 bg-white/80" rows={2} value={c.body} onChange={e => {709                              const val = e.target.value; setAbout(a => ({ ...a, cards: a.cards.map(x => x.id === c.id ? { ...x, body: val } : x) }));710                            }} />711                          </div>712                          <div>713                            <label className="block text-sm text-brand-inkSoft mb-1">الوسم</label>714                            <input className="w-full p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={c.tag || ""} onChange={e => {715                              const val = e.target.value; setAbout(a => ({ ...a, cards: a.cards.map(x => x.id === c.id ? { ...x, tag: val } : x) }));716                            }} />717                          </div>718                          <div className="flex items-end justify-end">719                            <button className="btn btn-ghost bg-red-50 text-red-700 border-red-200" onClick={() => setAbout(a => ({ ...a, cards: a.cards.filter(x => x.id !== c.id) }))}>حذف</button>720                          </div>721                        </div>722                      </div>723                    ))}724                  </div>725                </div>726              </div>727            </section>728          )}729 730          {tab === "news" && (731            <section className="admin-card">732              <div className="flex items-center justify-between mb-3">733                <h3 className="font-display text-brand-ink">إدارة: الأخبار</h3>734                <button className="btn btn-ghost" onClick={addNews}>خبر جديد</button>735              </div>736              <div className="space-y-3">737                {news.sort((a, b) => new Date(b.date) - new Date(a.date)).map(n => (738                  <div key={n.id} className="row-card">739                    <div className="flex-1">740                      <div className="font-semibold text-brand-ink">{n.title || "بدون عنوان"}</div>741                      <div className="text-xs text-brand-inkSoft">{formatDate(n.date)}</div>742                    </div>743                    <div className="flex items-center gap-2">744                      <label className="text-xs text-brand-inkSoft flex items-center gap-1">745                        <input746                          type="checkbox"747                          checked={n.published}748                          onChange={e => updateSection("news", { ...n, published: e.target.checked })}749                        />750                        منشور751                      </label>752                      <button className="btn btn-ghost bg-red-50 text-red-700 border-red-200" onClick={() => deleteItem("news", n.id)}>حذف</button>753                    </div>754                  </div>755                ))}756              </div>757            </section>758          )}759 760          {tab === "contact" && (761            <section className="admin-card">762              <div className="flex items-center justify-between mb-3">763                <h3 className="font-display text-brand-ink">إدارة: تواصل معنا</h3>764                <button className="btn btn-ghost" onClick={addContact}>إضافة قناة</button>765              </div>766              <div className="space-y-3">767                {contact.map(c => (768                  <div key={c.id} className="row-card">769                    <div className="flex-1 grid md:grid-cols-3 gap-3">770                      <input className="p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={c.label} onChange={e => updateSection("contact", { ...c, label: e.target.value })} placeholder="التسمية" />771                      <input className="p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={c.value} onChange={e => updateSection("contact", { ...c, value: e.target.value })} placeholder="القيمة" />772                      <select className="p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={c.type} onChange={e => updateSection("contact", { ...c, type: e.target.value })}>773                        <option value="text">نص</option>774                        <option value="email">بريد</option>775                        <option value="phone">هاتف</option>776                      </select>777                    </div>778                    <button className="btn btn-ghost bg-red-50 text-red-700 border-red-200" onClick={() => deleteItem("contact", c.id)}>حذف</button>779                  </div>780                ))}781              </div>782            </section>783          )}784 785          {tab === "donation" && (786            <section className="admin-card">787              <div className="flex items-center justify-between mb-3">788                <h3 className="font-display text-brand-ink">إدارة: التبرع</h3>789                <button className="btn btn-ghost" onClick={addDonation}>إضافة طريقة تبرع</button>790              </div>791              <div className="space-y-3">792                {donation.map(d => (793                  <div key={d.id} className="row-card">794                    <div className="flex-1 grid md:grid-cols-5 gap-3">795                      <input className="p-2 rounded-lg border border-brand-gold/40 bg-white/80" value={d.title} onChange={e => updateSection("donation", { ...d, title: e.target.value })} placeholder