Jelajahi Sumber

feat(ui): add global loading screen with rotating funny messages

- Enhanced LoadingScreen component with rotating messages support
- Added messages prop for array of funny loading messages (rotates every 2.5s)
- Added showAnimatedIcon prop for Brain+Sparkles+Loader animation
- Added subtitle prop for additional context text
- Added bouncing progress dots animation
- Added loading message categories in 3 languages (EN, HU, DE):
  - generic: general-purpose messages
  - ai: AI/ML-themed messages
  - sync: data synchronization messages
  - auth: authentication messages
- Updated CustomContentTextEntry to use the enhanced LoadingScreen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 4 bulan lalu
induk
melakukan
88f350596a

+ 15 - 72
shopcall.ai-main/src/components/CustomContentTextEntry.tsx

@@ -1,14 +1,11 @@
-import { useState, useEffect, useRef } from "react";
+import { useState } from "react";
 import { Button } from "@/components/ui/button";
 import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
 import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { Label } from "@/components/ui/label";
 import { Textarea } from "@/components/ui/textarea";
 import { Textarea } from "@/components/ui/textarea";
 import { Alert, AlertDescription } from "@/components/ui/alert";
 import { Alert, AlertDescription } from "@/components/ui/alert";
-import {
-  Dialog,
-  DialogContent,
-} from "@/components/ui/dialog";
-import { AlertCircle, Loader2, Sparkles, Brain } from "lucide-react";
+import { LoadingScreen } from "@/components/ui/loading-screen";
+import { AlertCircle } from "lucide-react";
 import { useToast } from "@/hooks/use-toast";
 import { useToast } from "@/hooks/use-toast";
 import { supabase } from "@/lib/supabase";
 import { supabase } from "@/lib/supabase";
 import { useShop } from "@/components/context/ShopContext";
 import { useShop } from "@/components/context/ShopContext";
@@ -54,39 +51,11 @@ export function CustomContentTextEntry({ onSuccess }: CustomContentTextEntryProp
   const [saving, setSaving] = useState(false);
   const [saving, setSaving] = useState(false);
   const [error, setError] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null);
   const [editorHasContent, setEditorHasContent] = useState(false);
   const [editorHasContent, setEditorHasContent] = useState(false);
-  const [loadingMessage, setLoadingMessage] = useState("");
-  const loadingIntervalRef = useRef<NodeJS.Timeout | null>(null);
   const { toast } = useToast();
   const { toast } = useToast();
   const { selectedShop } = useShop();
   const { selectedShop } = useShop();
 
 
-  // Get loading messages from translations
-  const loadingMessages = t('customContent.textEntry.savingMessages', { returnObjects: true }) as string[];
-
-  // Rotate loading messages while saving
-  useEffect(() => {
-    if (saving && Array.isArray(loadingMessages) && loadingMessages.length > 0) {
-      // Set initial message
-      setLoadingMessage(loadingMessages[Math.floor(Math.random() * loadingMessages.length)]);
-
-      // Rotate messages every 2.5 seconds
-      loadingIntervalRef.current = setInterval(() => {
-        setLoadingMessage(loadingMessages[Math.floor(Math.random() * loadingMessages.length)]);
-      }, 2500);
-    } else {
-      // Clear interval when not saving
-      if (loadingIntervalRef.current) {
-        clearInterval(loadingIntervalRef.current);
-        loadingIntervalRef.current = null;
-      }
-    }
-
-    return () => {
-      if (loadingIntervalRef.current) {
-        clearInterval(loadingIntervalRef.current);
-        loadingIntervalRef.current = null;
-      }
-    };
-  }, [saving, loadingMessages]);
+  // Get AI loading messages from translations
+  const loadingMessages = t('common.loadingMessages.ai', { returnObjects: true }) as string[];
 
 
   // Initialize TipTap editor for WYSIWYG mode
   // Initialize TipTap editor for WYSIWYG mode
   const editor = useEditor({
   const editor = useEditor({
@@ -329,42 +298,16 @@ export function CustomContentTextEntry({ onSuccess }: CustomContentTextEntryProp
         </Button>
         </Button>
       </div>
       </div>
 
 
-      {/* Loading Dialog */}
-      <Dialog open={saving} onOpenChange={() => {}}>
-        <DialogContent className="bg-slate-800 border-slate-700 sm:max-w-md" hideCloseButton>
-          <div className="flex flex-col items-center justify-center py-8 space-y-6">
-            {/* Animated Icon */}
-            <div className="relative">
-              <div className="w-20 h-20 rounded-full bg-gradient-to-br from-cyan-500/20 to-purple-500/20 flex items-center justify-center">
-                <Brain className="w-10 h-10 text-cyan-400 animate-pulse" />
-              </div>
-              <div className="absolute -top-1 -right-1">
-                <Sparkles className="w-6 h-6 text-yellow-400 animate-bounce" />
-              </div>
-              <div className="absolute -bottom-1 -left-1">
-                <Loader2 className="w-5 h-5 text-purple-400 animate-spin" />
-              </div>
-            </div>
-
-            {/* Loading Message */}
-            <div className="text-center space-y-2">
-              <p className="text-lg font-medium text-white animate-pulse">
-                {loadingMessage}
-              </p>
-              <p className="text-sm text-slate-400">
-                {t('customContent.textEntry.saving')}
-              </p>
-            </div>
-
-            {/* Progress dots */}
-            <div className="flex space-x-2">
-              <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
-              <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
-              <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
-            </div>
-          </div>
-        </DialogContent>
-      </Dialog>
+      {/* Loading Screen Overlay */}
+      {saving && (
+        <div className="fixed inset-0 z-50">
+          <LoadingScreen
+            messages={loadingMessages}
+            showAnimatedIcon
+            subtitle={t('customContent.textEntry.saving')}
+          />
+        </div>
+      )}
     </div>
     </div>
   );
   );
 }
 }

+ 76 - 14
shopcall.ai-main/src/components/ui/loading-screen.tsx

@@ -1,39 +1,101 @@
-import { LucideIcon, Loader2 } from "lucide-react";
+import { useState, useEffect } from "react";
+import { LucideIcon, Loader2, Brain, Sparkles } from "lucide-react";
 import { useTranslation } from "react-i18next";
 import { useTranslation } from "react-i18next";
 
 
 interface LoadingScreenProps {
 interface LoadingScreenProps {
-  /** Optional message to display under the spinner */
+  /** Optional single message to display under the spinner */
   message?: string;
   message?: string;
+  /** Optional array of messages to rotate through (every 2.5 seconds) */
+  messages?: string[];
   /** Optional icon to display in the center of the spinner. Defaults to a generic loading icon */
   /** Optional icon to display in the center of the spinner. Defaults to a generic loading icon */
   icon?: LucideIcon;
   icon?: LucideIcon;
   /** Whether to show the full-screen gradient background. Defaults to true */
   /** Whether to show the full-screen gradient background. Defaults to true */
   fullScreen?: boolean;
   fullScreen?: boolean;
+  /** Show animated Brain+Sparkles icon instead of standard spinner icon. Defaults to false */
+  showAnimatedIcon?: boolean;
+  /** Optional subtitle to display below the main message */
+  subtitle?: string;
 }
 }
 
 
 /**
 /**
  * A reusable loading screen component with an animated circular spinner.
  * A reusable loading screen component with an animated circular spinner.
  * Used throughout the application for consistent loading states.
  * Used throughout the application for consistent loading states.
+ *
+ * Supports rotating funny messages when `messages` array is provided.
  */
  */
 export function LoadingScreen({
 export function LoadingScreen({
   message,
   message,
+  messages,
   icon: Icon = Loader2,
   icon: Icon = Loader2,
-  fullScreen = true
+  fullScreen = true,
+  showAnimatedIcon = false,
+  subtitle
 }: LoadingScreenProps) {
 }: LoadingScreenProps) {
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const [currentMessage, setCurrentMessage] = useState<string>("");
+
+  // Rotate through messages every 2.5 seconds
+  useEffect(() => {
+    if (messages && messages.length > 0) {
+      // Set initial random message
+      setCurrentMessage(messages[Math.floor(Math.random() * messages.length)]);
+
+      // Rotate every 2.5 seconds
+      const interval = setInterval(() => {
+        setCurrentMessage(messages[Math.floor(Math.random() * messages.length)]);
+      }, 2500);
+
+      return () => clearInterval(interval);
+    }
+  }, [messages]);
+
+  // Determine which message to display
+  const displayMessage = messages && messages.length > 0
+    ? currentMessage
+    : (message || t('common.loading'));
+
+  // Animated icon content (Brain + Sparkles + Loader)
+  const animatedIconContent = (
+    <div className="relative mb-6">
+      <div className="w-20 h-20 rounded-full bg-gradient-to-br from-cyan-500/20 to-purple-500/20 flex items-center justify-center">
+        <Brain className="w-10 h-10 text-cyan-400 animate-pulse" />
+      </div>
+      <Sparkles className="absolute -top-1 -right-1 w-6 h-6 text-yellow-400 animate-bounce" />
+      <Loader2 className="absolute -bottom-1 -left-1 w-5 h-5 text-purple-400 animate-spin" />
+    </div>
+  );
+
+  // Standard icon content (existing spinner)
+  const standardIconContent = (
+    <div className="relative mx-auto w-16 h-16 mb-6">
+      {/* Outer static ring */}
+      <div className="absolute inset-0 rounded-full border-4 border-[#52b3d0]/20"></div>
+      {/* Spinning ring */}
+      <div className="absolute inset-0 rounded-full border-4 border-transparent border-t-[#52b3d0] animate-spin"></div>
+      {/* Inner circle with icon */}
+      <div className="absolute inset-2 rounded-full bg-[#52b3d0]/10 flex items-center justify-center">
+        <Icon className="w-6 h-6 text-[#52b3d0]" />
+      </div>
+    </div>
+  );
 
 
   const content = (
   const content = (
-    <div className="w-full max-w-md text-center">
-      <div className="relative mx-auto w-16 h-16 mb-6">
-        {/* Outer static ring */}
-        <div className="absolute inset-0 rounded-full border-4 border-[#52b3d0]/20"></div>
-        {/* Spinning ring */}
-        <div className="absolute inset-0 rounded-full border-4 border-transparent border-t-[#52b3d0] animate-spin"></div>
-        {/* Inner circle with icon */}
-        <div className="absolute inset-2 rounded-full bg-[#52b3d0]/10 flex items-center justify-center">
-          <Icon className="w-6 h-6 text-[#52b3d0]" />
+    <div className="w-full max-w-md text-center flex flex-col items-center">
+      {showAnimatedIcon ? animatedIconContent : standardIconContent}
+      <p className={`text-slate-400 ${messages && messages.length > 0 ? 'animate-pulse' : ''}`}>
+        {displayMessage}
+      </p>
+      {subtitle && (
+        <p className="text-sm text-slate-500 mt-2">{subtitle}</p>
+      )}
+      {/* Progress dots for rotating messages */}
+      {messages && messages.length > 0 && (
+        <div className="flex space-x-2 mt-4">
+          <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
+          <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
+          <div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
         </div>
         </div>
-      </div>
-      <p className="text-slate-400">{message || t('common.loading')}</p>
+      )}
     </div>
     </div>
   );
   );
 
 

+ 51 - 1
shopcall.ai-main/src/i18n/locales/de.json

@@ -259,7 +259,57 @@
     "hoursAgo": "vor {{count}} Std.",
     "hoursAgo": "vor {{count}} Std.",
     "daysAgo": "vor {{count}} Tagen",
     "daysAgo": "vor {{count}} Tagen",
     "free": "Kostenlos",
     "free": "Kostenlos",
-    "note": "Hinweis"
+    "note": "Hinweis",
+    "loadingMessages": {
+      "generic": [
+        "Einen Moment, Magie passiert...",
+        "Nur einen Augenblick, wir arbeiten daran...",
+        "Großartiges wird geladen...",
+        "Bitte warten, coole Sachen passieren...",
+        "Fast fertig, bleiben Sie dran...",
+        "Wir brauen etwas Besonderes...",
+        "Es passiert gerade einiges...",
+        "Halten Sie durch, fast geschafft...",
+        "Wir zaubern...",
+        "Alles wird vorbereitet..."
+      ],
+      "ai": [
+        "KI lernt neue Tricks...",
+        "Wissen wird dem Roboter gefüttert...",
+        "Weisheit wird in Vektoren umgewandelt...",
+        "Machine Learning Magie wird gestreut...",
+        "Inhalt wird KI-lesbar gemacht...",
+        "Gehirnnahrung für den Bot wird hochgeladen...",
+        "Text wird in Roboterkraftstoff umgewandelt...",
+        "Inhalt wird in die Cloud gebeamt...",
+        "Kleine digitale Neuronen werden trainiert...",
+        "Wissen wird in die Matrix eingebettet..."
+      ],
+      "sync": [
+        "Synchronisierung mit Ihrem Shop...",
+        "Frische Daten werden abgerufen...",
+        "Neueste Updates werden geladen...",
+        "Verbindung zur Cloud wird hergestellt...",
+        "Ihre Informationen werden gesammelt...",
+        "Dashboard wird aktualisiert...",
+        "Shop-Daten werden heruntergeladen...",
+        "Nach Updates wird gesucht...",
+        "Alles wird synchronisiert...",
+        "Ihre Produkte werden geladen..."
+      ],
+      "auth": [
+        "Anmeldedaten werden überprüft...",
+        "Sitzung wird validiert...",
+        "Verbindung wird gesichert...",
+        "Ihre Identität wird verifiziert...",
+        "Ihr Arbeitsbereich wird eingerichtet...",
+        "Dashboard wird vorbereitet...",
+        "Ihr Profil wird geladen...",
+        "Sichere Authentifizierung läuft...",
+        "Sie werden angemeldet...",
+        "Fast eingeloggt..."
+      ]
+    }
   },
   },
   "signup": {
   "signup": {
     "title": "Registrieren",
     "title": "Registrieren",

+ 51 - 1
shopcall.ai-main/src/i18n/locales/en.json

@@ -259,7 +259,57 @@
     "hoursAgo": "{{count}} hours ago",
     "hoursAgo": "{{count}} hours ago",
     "daysAgo": "{{count}} days ago",
     "daysAgo": "{{count}} days ago",
     "free": "Free",
     "free": "Free",
-    "note": "Note"
+    "note": "Note",
+    "loadingMessages": {
+      "generic": [
+        "Hang tight, magic in progress...",
+        "Just a moment, working on it...",
+        "Loading awesomeness...",
+        "Please wait, doing cool stuff...",
+        "Almost there, stay tuned...",
+        "Brewing something special...",
+        "Making things happen...",
+        "Hold on, nearly done...",
+        "Working our magic...",
+        "Getting everything ready..."
+      ],
+      "ai": [
+        "Teaching AI new tricks...",
+        "Feeding knowledge to the robot...",
+        "Converting wisdom into vectors...",
+        "Sprinkling some machine learning magic...",
+        "Making your content AI-readable...",
+        "Uploading brain food for the bot...",
+        "Turning text into robot fuel...",
+        "Beaming content to the cloud...",
+        "Training tiny digital neurons...",
+        "Embedding knowledge in the matrix..."
+      ],
+      "sync": [
+        "Syncing with your store...",
+        "Fetching fresh data...",
+        "Pulling the latest updates...",
+        "Connecting to the cloud...",
+        "Gathering your information...",
+        "Refreshing your dashboard...",
+        "Downloading store data...",
+        "Checking for updates...",
+        "Synchronizing everything...",
+        "Loading your products..."
+      ],
+      "auth": [
+        "Checking your credentials...",
+        "Validating your session...",
+        "Securing the connection...",
+        "Verifying your identity...",
+        "Setting up your workspace...",
+        "Preparing your dashboard...",
+        "Loading your profile...",
+        "Authenticating securely...",
+        "Getting you signed in...",
+        "Almost logged in..."
+      ]
+    }
   },
   },
   "signup": {
   "signup": {
     "title": "Sign Up",
     "title": "Sign Up",

+ 51 - 1
shopcall.ai-main/src/i18n/locales/hu.json

@@ -259,7 +259,57 @@
     "hoursAgo": "{{count}} órája",
     "hoursAgo": "{{count}} órája",
     "daysAgo": "{{count}} napja",
     "daysAgo": "{{count}} napja",
     "free": "Ingyenes",
     "free": "Ingyenes",
-    "note": "Megjegyzés"
+    "note": "Megjegyzés",
+    "loadingMessages": {
+      "generic": [
+        "Egy pillanat, varázslat folyamatban...",
+        "Kis türelmet, dolgozunk rajta...",
+        "Töltjük a jóságot...",
+        "Kérjük várjon, menő dolgokat csinálunk...",
+        "Majdnem kész, maradjon velünk...",
+        "Valami különlegeset készítünk...",
+        "Dolgok történnek...",
+        "Tartson ki, mindjárt kész...",
+        "Varázslunk...",
+        "Mindent előkészítünk..."
+      ],
+      "ai": [
+        "AI trükköket tanítunk...",
+        "Tudást etetünk a robottal...",
+        "Bölcsességet alakítunk vektorokká...",
+        "Gépi tanulás varázslatot szórunk...",
+        "AI-olvashatóvá tesszük a tartalmat...",
+        "Agyeleséget töltünk fel a botnak...",
+        "Szöveget robot üzemanyaggá alakítunk...",
+        "Tartalmat sugárzunk a felhőbe...",
+        "Apró digitális neuronokat képzünk...",
+        "Tudást ágyazunk a mátrixba..."
+      ],
+      "sync": [
+        "Szinkronizálás a bolttal...",
+        "Friss adatok betöltése...",
+        "Legújabb frissítések letöltése...",
+        "Kapcsolódás a felhőhöz...",
+        "Információk összegyűjtése...",
+        "Irányítópult frissítése...",
+        "Bolt adatainak letöltése...",
+        "Frissítések keresése...",
+        "Minden szinkronizálása...",
+        "Termékek betöltése..."
+      ],
+      "auth": [
+        "Hitelesítő adatok ellenőrzése...",
+        "Munkamenet érvényesítése...",
+        "Kapcsolat biztosítása...",
+        "Személyazonosság ellenőrzése...",
+        "Munkaterület előkészítése...",
+        "Irányítópult előkészítése...",
+        "Profil betöltése...",
+        "Biztonságos hitelesítés...",
+        "Bejelentkeztetjük...",
+        "Majdnem bejelentkezve..."
+      ]
+    }
   },
   },
   "signup": {
   "signup": {
     "title": "Regisztráció",
     "title": "Regisztráció",