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
47 changes: 23 additions & 24 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
# 📌 Pull Request Description
## Description

## 🔗 Related Issue
<!-- Briefly describe what this PR does -->

Closes #
## Related Issue

## 📝 Description
<!-- Link to the related issue -->
Fixes #<!-- issue number -->

Please include a summary of the changes and the related issue.
## Type of Change

## ✅ Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Configuration change

* [ ] Bug fix
* [ ] New feature
* [ ] Documentation update
* [x] Chore
* [ ] UI/UX improvement
* [ ] Other
## Checklist

## 🧪 Testing Done
- [ ] My code follows the project's coding style
- [ ] I have performed a self-review of my code
- [ ] I have commented my code where necessary
- [ ] I have updated the documentation accordingly
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or my feature works
- [ ] New and existing unit tests pass locally with my changes

* [ ] Tested locally
* [ ] Existing functionality verified
* [ ] No new warnings/errors introduced
## Screenshots (if applicable)

## 📸 Screenshots (if applicable)
<!-- Add screenshots to show visual changes -->

Add screenshots or proof here.
## Additional Notes

## ✔️ Checklist

* [ ] My code follows the project guidelines
* [ ] I reviewed my own changes
* [ ] I linked the related issue
* [ ] This PR targets the `gssoc` branch
<!-- Any additional information that reviewers should know -->
7 changes: 5 additions & 2 deletions Frontend/src/admin/pages/AdminSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import {
} from '../../utils/adminSettingsPersistence';

import { Select } from "../../components/ui/select";
import useAuthStore from '../../store/authStore';
import { supabase } from '../../lib/supabaseClient';

/**
* AdminSettings Page
Expand Down Expand Up @@ -115,6 +113,11 @@ const AdminSettings = () => {
setStatusMessage('Saving changes...');
};

const digestEnabled = settings.digestEnabled || false;
const handleDigestToggle = () => {
handleChange('digestEnabled', !digestEnabled);
};

const handleSaveSettings = useCallback(() => {
saveCompanySettings(settings);
}, [saveCompanySettings, settings]);
Expand Down
110 changes: 110 additions & 0 deletions Frontend/src/components/ErrorBoundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null, copied: false };
}

static getDerivedStateFromError(error) {
return { hasError: true, error };
}

componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
console.error("[Unhandled Error Caught by Boundary]:", error, errorInfo);
}

handleCopyPayload = () => {
const payload = {
error: this.state.error?.toString() || "Unknown error",
stack: this.state.error?.stack || "No stack trace available",
componentStack: this.state.errorInfo?.componentStack || "No component stack available",
url: window.location.href,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
};

navigator.clipboard.writeText(JSON.stringify(payload, null, 2))
.then(() => {
this.setState({ copied: true });
setTimeout(() => this.setState({ copied: false }), 2000);
})
.catch(err => {
console.error("Failed to copy error payload:", err);
});
};

handleReset = () => {
window.location.reload();
};

render() {
if (this.state.hasError) {
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-[#0f172a] text-[#f8fafc] p-6 relative overflow-hidden font-sans">
{/* Background Orbs */}
<div className="absolute top-[-10%] left-[-10%] w-[50vw] h-[50vw] rounded-full bg-emerald-600/10 blur-[130px] pointer-events-none"></div>
<div className="absolute bottom-[-10%] right-[-10%] w-[50vw] h-[50vw] rounded-full bg-blue-600/10 blur-[130px] pointer-events-none"></div>

<div className="max-w-2xl w-full bg-slate-900/60 backdrop-blur-xl border border-white/10 p-8 rounded-2xl shadow-2xl relative z-10 text-center">
<div className="mb-6 flex justify-center">
<div className="bg-red-500/20 p-4 rounded-full border border-red-500/30 animate-pulse">
<svg className="w-12 h-12 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
</div>

<h1 className="text-3xl font-extrabold mb-2 tracking-tight text-white">System Interrupted</h1>
<p className="text-slate-400 mb-6">Our Neural Engine encountered an unhandled execution exception. The diagnostic log has been secured.</p>

<div className="bg-slate-950/80 border border-slate-800 rounded-xl p-5 mb-6 text-left font-mono text-xs overflow-auto max-h-60 text-red-400 select-text">
<p className="font-bold text-white mb-2">Error: {this.state.error?.message || this.state.error?.toString()}</p>
<p className="text-slate-500 whitespace-pre">{this.state.error?.stack || "No stack trace secured."}</p>
{this.state.errorInfo?.componentStack && (
<p className="text-slate-600 mt-4 whitespace-pre">{this.state.errorInfo.componentStack}</p>
)}
</div>

<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<button
onClick={this.handleCopyPayload}
className="w-full sm:w-auto px-6 py-3 rounded-xl font-bold transition-all duration-200 border border-slate-700 bg-slate-800/80 hover:bg-slate-700 hover:text-white flex items-center justify-center gap-2"
>
{this.state.copied ? (
<>
<svg className="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
<span>Copied Diagnostics</span>
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<span>Copy Diagnostics Payload</span>
</>
)}
</button>
<button
onClick={this.handleReset}
className="w-full sm:w-auto px-6 py-3 bg-emerald-500 hover:bg-emerald-400 text-slate-950 rounded-xl font-extrabold transition-all duration-200 shadow-lg shadow-emerald-500/20 hover:shadow-emerald-500/30 flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 8H18.2" />
</svg>
<span>Recover & Restart</span>
</button>
</div>
</div>
</div>
);
}

return this.props.children;
}
}

export default ErrorBoundary;
2 changes: 1 addition & 1 deletion Frontend/src/docs/pages/DocsPortal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const DocsPortal = () => {
"Routed based on neural network rule matching"
]
}, null, 2));
} catch (e) {
} catch {
setSandboxOutput(JSON.stringify({
status: "error",
message: "Invalid JSON format in Request Payload."
Expand Down
5 changes: 4 additions & 1 deletion Frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import ErrorBoundary from './components/ErrorBoundary.jsx'

createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
<ErrorBoundary>
<App />
</ErrorBoundary>
</StrictMode>,
)
5 changes: 1 addition & 4 deletions Frontend/src/pages/AdminSignup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -448,15 +448,12 @@ function AdminSignup() {
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
{/* Strength Meter */}
{formData.password && (
<div className="mt-2 space-y-2">
<div className="flex justify-between items-center text-[10px] font-bold uppercase tracking-widest text-gray-400">
<span>Strength: {getStrengthText()}</span>
<span>{passwordStrength}%</span>
</div>
)}
{formData.password && (
<div className="h-1 w-full bg-gray-100 dark:bg-emerald-950/40 rounded-full overflow-hidden">
<motion.div
className={`h-full ${getStrengthColor()}`}
Expand All @@ -471,7 +468,7 @@ function AdminSignup() {
{passwordWarning || "Password requirements met."}
</div>
</div>
</div>
)}
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider flex items-center gap-2">
Expand Down
40 changes: 2 additions & 38 deletions Frontend/src/pages/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useAuthStore from "../store/authStore";
import { Eye, EyeOff, BrainCircuit, ArrowRight, Loader2, ArrowLeft } from "lucide-react";

function Login() {
const isDark = document.documentElement.classList.contains('dark');
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
Expand Down Expand Up @@ -123,29 +124,6 @@ function Login() {
}
};

const handleGoogleLogin = async () => {
try {
setError("");
await loginWithGoogle();
} catch (err) {
console.error("Google login error:", err);
setError(err.message || "Google Sign-In failed.");
}
};

const handleGoogleLogin = async () => {
setError("");
try {
await signInWithGoogle();
} catch (err) {
console.error("Google login error:", err);
let errMsg = err.message || "Failed to sign in with Google.";
if (errMsg.toLowerCase().includes("failed to fetch")) {
errMsg = "Network Error: Failed to fetch. Please try disabling your ad-blocker for this site and refresh!";
}
setError(errMsg);
}
};

const currentSubmitHandler = isMagicLink ? handleMagicLink : handleLogin;

Expand Down Expand Up @@ -226,21 +204,7 @@ function Login() {
<span style={{ fontWeight: 800, fontSize: '16px', color: '#0f1f12' }}>HelpDesk.ai</span>
</Link>
</div>
<h2
style={{
fontFamily: "'Syne', sans-serif",
fontSize: '28px',
fontWeight: 800,
color: isDark ? '#ffffff' : '#0f1f12',
letterSpacing: '-0.02em',
marginBottom: '8px',
}}
>
<div className="p-2 rounded-full bg-slate-50 dark:bg-[#1a2e24] border border-slate-200 dark:border-[#2a4034] group-hover:border-emerald-500 transition-colors">
<ArrowLeft className="w-4 h-4" />
</div>
<span className="text-sm font-semibold">Back to Home</span>
</Link>


{/* Header */}
<div className="text-center mb-8">
Expand Down
12 changes: 2 additions & 10 deletions Frontend/src/pages/Signup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function Signup() {

const dropdownRef = useRef(null);
const navigate = useNavigate();
const { signup, user, profile } = useAuthStore();
const { signup, loginWithGoogle, loading, user, profile } = useAuthStore();
const passwordRules = { minLength: 6 };
const passwordChecks = getPasswordValidation(password, passwordRules);
const passwordWarning = getPasswordValidationMessage(passwordChecks, passwordRules);
Expand Down Expand Up @@ -108,15 +108,7 @@ function Signup() {
e.preventDefault();
setError("");

// Password complexity validator — mirrors Supabase's policy
const validatePassword = (pw) => {
if (pw.length < 8) return 'Password must be at least 8 characters long.';
if (!/[a-z]/.test(pw)) return 'Password must contain at least one lowercase letter (a-z).';
if (!/[A-Z]/.test(pw)) return 'Password must contain at least one uppercase letter (A-Z).';
if (!/[0-9]/.test(pw)) return 'Password must contain at least one number (0-9).';
if (!/[^A-Za-z0-9]/.test(pw)) return 'Password must contain at least one special character.';
return null;
};


if (!email || !password || !confirmPassword || !fullName) {
setError("All fields are required.");
Expand Down
2 changes: 2 additions & 0 deletions Frontend/src/store/adminStore.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { safePersistStorage } from './persistentStorage';

const useAdminStore = create(
persist(
Expand All @@ -19,6 +20,7 @@ const useAdminStore = create(
}),
{
name: 'admin-storage',
storage: safePersistStorage,
}
)
);
Expand Down
2 changes: 2 additions & 0 deletions Frontend/src/store/authStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { persist } from 'zustand/middleware';
import { supabase } from '../lib/supabaseClient';
import { API_CONFIG } from '../config';
import useTicketStore from './ticketStore';
import { safePersistStorage } from './persistentStorage';

const BACKEND_URL = API_CONFIG.BACKEND_URL;

Expand Down Expand Up @@ -416,6 +417,7 @@ const useAuthStore = create(
}),
{
name: 'auth-storage',
storage: safePersistStorage,
partialize: (state) => ({
// Cache display-only profile fields. Role/status must come from the DB.
profile: getProfileCache(state.profile)
Expand Down
40 changes: 40 additions & 0 deletions Frontend/src/store/persistentStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createJSONStorage } from 'zustand/middleware';

// In-memory fallback cache when localStorage is unavailable (e.g. private mode, quota limits)
const inMemoryCache = {};

export const robustLocalStorage = {
getItem: (name) => {
try {
return localStorage.getItem(name);
} catch (error) {
console.warn(`[PersistentStorage Read Error] Failed to read key "${name}" from localStorage. Falling back to memory.`, error);
return inMemoryCache[name] || null;
}
},
setItem: (name, value) => {
try {
localStorage.setItem(name, value);
} catch (error) {
console.error(`[PersistentStorage Write Error] Failed to write key "${name}" to localStorage. Falling back to memory.`, error);
inMemoryCache[name] = value;

// Dispatch custom event to notify components or loggers about storage failure (e.g. QuotaExceededError)
const isQuotaExceeded = error.name === 'QuotaExceededError' || error.code === 22;
window.dispatchEvent(new CustomEvent('persistent-storage-error', {
detail: { name, error, isQuotaExceeded }
}));
}
},
removeItem: (name) => {
try {
localStorage.removeItem(name);
delete inMemoryCache[name];
} catch (error) {
console.warn(`[PersistentStorage Remove Error] Failed to remove key "${name}" from localStorage.`, error);
delete inMemoryCache[name];
}
}
};

export const safePersistStorage = createJSONStorage(() => robustLocalStorage);
Loading
Loading