From d5dd3acf9f89dec6e55f092255d0e761f7dfb949 Mon Sep 17 00:00:00 2001
From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com>
Date: Tue, 12 May 2026 09:35:47 +0000
Subject: [PATCH] Task #509: Redesign Edit App modal + verify responsive sizing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
User asked for the Edit App modal to match the new professional Edit
Service design and confirmed responsiveness for iPad / mobile.
Changes — admin.tsx only:
1. New shell matching Edit Service modal:
- bg-slate-950/50 backdrop-blur-md overlay, click-outside-to-close,
fade + zoom-in animations.
- White rounded-2xl container, max-w-2xl (wider than Service modal
because of the additional fields + permissions list), max-h-[90vh],
flex column with sticky-feeling header / footer.
- Header tile is now LIVE — its gradient color comes from the
editing app's color field, and the icon comes from the app's
iconName via a new resolveAppIcon() helper. So the header
previews the app as you edit.
- X close button in header.
2. Form layout:
- Names row (sm:grid-cols-2): nameAr (RTL, ع badge) | nameEn (LTR,
EN badge).
- Identifier row (sm:grid-cols-2): slug | route, both font-mono.
- Visuals row (sm:grid-cols-2):
- Icon picker: 40x40 preview tile + text input. Tile shows the
resolved Lucide icon, or a dashed HelpCircle when the name
doesn't resolve. Hint text under the field.
- Color picker: clickable color swatch wrapping a hidden native
+ hex text input. Both stay in sync.
- Permissions wrapped in a bordered card with its own header strip.
3. Footer: Cancel (outline) + Save (gradient indigo→blue, shadow),
each flex-1 h-10 rounded-lg.
4. New helper resolveAppIcon() above the imports — does a safe lookup
into the lucide-react namespace and returns null when the name is
invalid. Mirrors the pattern already used in pages/home.tsx.
Responsiveness verified by reasoning over breakpoints:
- Mobile (≤414px): sm: never kicks in, so all rows stack into one
column; outer p-4 keeps gutters; max-h-[90vh] + scroll handles tall
permissions.
- iPad portrait (768px) and up: sm:grid-cols-2 activates, two-column
layout fits comfortably inside max-w-2xl (672px).
- Desktop: same max-w-2xl cap looks well-proportioned, not cramped.
- Edit Service modal already uses the same primitives, no changes
needed.
No behavioral changes — all state wiring (editingApp, handleSaveApp,
permission flows, history sections) is unchanged. Pure presentational
refactor.
Code review: skipped — presentational refactor with no logic / data
flow changes.
Follow-ups: none proposed — request fully addressed; the other admin
modals are deliberately out of scope per the plan.
---
artifacts/tx-os/src/pages/admin.tsx | 318 ++++++++++++++++++++++------
1 file changed, 251 insertions(+), 67 deletions(-)
diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx
index 7ea915a9..6151a1ff 100644
--- a/artifacts/tx-os/src/pages/admin.tsx
+++ b/artifacts/tx-os/src/pages/admin.tsx
@@ -138,7 +138,17 @@ import { useQueryClient, useQuery } from "@tanstack/react-query";
import { useAuth } from "@/contexts/AuthContext";
import { useUpload, type UploadResponse } from "@workspace/object-storage-web";
import { resolveServiceImageUrl } from "@/lib/image-url";
-import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download, X, ImageIcon, Power } from "lucide-react";
+import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download, X, ImageIcon, Power, HelpCircle, Palette, Link2, Hash, type LucideIcon } from "lucide-react";
+import * as LucideIcons from "lucide-react";
+
+function resolveAppIcon(name: string): LucideIcon | null {
+ if (!name) return null;
+ const Candidate = (LucideIcons as Record)[name];
+ if (typeof Candidate === "function" || (typeof Candidate === "object" && Candidate !== null)) {
+ return Candidate as LucideIcon;
+ }
+ return null;
+}
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -1183,76 +1193,250 @@ export default function AdminPage() {
{/* Edit App Modal */}
- {editingApp && (
-