Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions Frontend/src/components/shared/LanguageToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Globe, ChevronDown, CheckCircle2 } from 'lucide-react';
import { SUPPORTED_LANGUAGES } from '../../services/translationService';

const LanguageToggle = ({ selectedLanguage, onLanguageChange, compact = false }) => {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);

useEffect(() => {
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) setIsOpen(false);
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

const currentLang = SUPPORTED_LANGUAGES.find(l => l.code === selectedLanguage);

if (compact) {
return (
<div className="relative" ref={ref}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-1.5 px-2.5 py-1.5 bg-white/80 border border-gray-200 rounded-lg text-xs font-bold text-gray-600 hover:bg-white hover:border-emerald-200 transition-all shadow-sm"
>
<Globe size={14} className="text-emerald-500" />
<span>{currentLang?.code?.toUpperCase() || 'EN'}</span>
<ChevronDown size={12} className={`transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: 8, scale: 0.95 }}
animate={{ opacity: 1, y: 4, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.95 }}
className="absolute right-0 z-50 mt-1 bg-white border border-gray-100 rounded-xl shadow-xl shadow-emerald-900/10 p-1.5 min-w-[160px]"
>
{SUPPORTED_LANGUAGES.map(lang => (
<button
key={lang.code}
onClick={() => { onLanguageChange(lang.code); setIsOpen(false); }}
className={`w-full text-left px-3 py-2 rounded-lg text-xs font-bold transition-all flex items-center justify-between
${selectedLanguage === lang.code ? 'bg-emerald-50 text-emerald-700' : 'text-gray-600 hover:bg-gray-50'}`}
>
<span>{lang.nativeName || lang.label}</span>
{selectedLanguage === lang.code && <CheckCircle2 size={12} className="text-emerald-500" />}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}

return (
<div className="relative" ref={ref}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 bg-gray-50 border border-gray-100 rounded-xl px-4 py-2.5 text-sm font-bold text-gray-700 hover:bg-white hover:border-emerald-200 transition-all shadow-sm group"
>
<Globe size={14} className="text-emerald-500" />
<span>{currentLang?.label || 'English'}</span>
<ChevronDown size={16} className={`text-gray-400 group-hover:text-emerald-500 transition-all ${isOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 5, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute right-0 z-50 mt-1 bg-white border border-gray-100 rounded-2xl shadow-2xl shadow-emerald-900/10 p-2 min-w-[200px]"
>
<div className="max-h-[260px] overflow-y-auto space-y-1">
{SUPPORTED_LANGUAGES.map(lang => (
<button
key={lang.code}
onClick={() => { onLanguageChange(lang.code); setIsOpen(false); }}
className={`w-full text-left px-4 py-2.5 rounded-xl text-sm font-bold transition-all flex items-center justify-between
${selectedLanguage === lang.code ? 'bg-emerald-50 text-emerald-700' : 'text-gray-600 hover:bg-gray-50'}`}
>
<span>{lang.label}</span>
{selectedLanguage === lang.code && <CheckCircle2 size={14} className="text-emerald-500" />}
</button>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};

export default LanguageToggle;
17 changes: 16 additions & 1 deletion Frontend/src/user/pages/AutoResolveChat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import useTicketStore from '../../store/ticketStore';
import { Card, CardContent } from "../../components/ui/card";
import { askAI } from '../../services/aiAssistant';
import useToastStore from '../../store/toastStore';
import LanguageToggle from "../../components/shared/LanguageToggle";
import { translateText } from '../../services/translationService';

const AutoResolveChat = () => {
const { aiTicket } = useTicketStore();
Expand All @@ -26,6 +28,14 @@ const AutoResolveChat = () => {
const { showToast } = useToastStore();
const scrollRef = useRef(null);
const fileInputRef = useRef(null);
const [selectedLanguage, setSelectedLanguage] = useState('en');

const handleLanguageChange = (langCode) => {
setSelectedLanguage(langCode);
if (langCode !== 'en') {
localStorage.setItem('preferredLanguage', langCode);
}
};

// Initial Plan Generation
useEffect(() => {
Expand Down Expand Up @@ -238,7 +248,12 @@ const AutoResolveChat = () => {
</div>

{/* "Escalate Anyway" Integrated Option */}
<div className="flex gap-3">
<div className="flex gap-3 items-center">
<LanguageToggle
selectedLanguage={selectedLanguage}
onLanguageChange={handleLanguageChange}
compact
/>
<button
onClick={() => navigate('/ticket-tracking')}
className="group px-6 py-2.5 bg-slate-900/5 hover:bg-slate-900 text-slate-600 hover:text-white font-black text-[10px] uppercase tracking-widest rounded-xl transition-all duration-300 flex items-center gap-2 border border-slate-200/50 hover:border-slate-900"
Expand Down
2 changes: 1 addition & 1 deletion Frontend/src/user/pages/CreateTicket.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const CreateTicket = () => {
const navigate = useNavigate();
const MAX_CHARS = 1000;
const supportsSpeech = 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
const [selectedLanguage, setSelectedLanguage] = useState('en');
const [selectedLanguage, setSelectedLanguage] = useState(() => localStorage.getItem('preferredLanguage') || 'en');
const [isTranslating, setIsTranslating] = useState(false);
const [isLangOpen, setIsLangOpen] = useState(false);
const langRef = useRef(null);
Expand Down
22 changes: 20 additions & 2 deletions Frontend/src/user/pages/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import React from 'react';
import React, { useState } from 'react';
import WelcomeCard from "../components/WelcomeCard";
import QuickActions from "../components/QuickActions";
import RecentTickets from "../components/RecentTickets";
import OnboardingTour from "../components/OnboardingTour";
import LanguageToggle from "../../components/shared/LanguageToggle";
import { translateText } from '../../services/translationService';

import useAuthStore from "../../store/authStore";

const Dashboard = () => {
const { profile } = useAuthStore();
const userName = profile?.full_name || "Guest";
const [selectedLanguage, setSelectedLanguage] = useState('en');

const handleLanguageChange = (langCode) => {
setSelectedLanguage(langCode);
if (langCode !== 'en') {
// Language preference change noted; translateText is used by child components
localStorage.setItem('preferredLanguage', langCode);
}
};

return (
<div
Expand All @@ -25,8 +36,15 @@ const Dashboard = () => {
<main className="pt-24 px-6 md:px-10">
<div className="w-full max-w-[1100px] mx-auto flex flex-col gap-8">
{/* Hero Section */}
<section>
<section className="flex items-start justify-between">
<WelcomeCard userName={userName} />
<div className="shrink-0 mt-2">
<LanguageToggle
selectedLanguage={selectedLanguage}
onLanguageChange={handleLanguageChange}
compact
/>
</div>
</section>

{/* Quick Actions Grid */}
Expand Down
Loading