diff --git a/app/api/support/bug-report/route.ts b/app/api/support/bug-report/route.ts new file mode 100644 index 00000000..8e32dc5c --- /dev/null +++ b/app/api/support/bug-report/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server' +import { createClient } from '@/lib/supabase/server' + +export async function POST(request: NextRequest) { + try { + const supabase = await createClient() + + // Get authenticated user + const { data: { user }, error: authError } = await supabase.auth.getUser() + + if (authError || !user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const { title, description } = await request.json() + + if (!title || !description) { + return NextResponse.json({ error: 'Title and description are required' }, { status: 400 }) + } + + // Insert bug report into database + const { error: insertError } = await supabase + .from('support_tickets') + .insert({ + user_id: user.id, + type: 'bug', + subject: title, + message: description, + status: 'open', + }) + + if (insertError) { + console.error('Error creating bug report:', insertError) + return NextResponse.json({ error: 'Failed to submit bug report' }, { status: 500 }) + } + + return NextResponse.json({ success: true, message: 'Bug report submitted successfully' }) + } catch (error) { + console.error('Error in bug report API:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/app/api/support/contact/route.ts b/app/api/support/contact/route.ts new file mode 100644 index 00000000..34f94240 --- /dev/null +++ b/app/api/support/contact/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server' +import { createClient } from '@/lib/supabase/server' + +export async function POST(request: NextRequest) { + try { + const supabase = await createClient() + + // Get authenticated user + const { data: { user }, error: authError } = await supabase.auth.getUser() + + if (authError || !user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const { subject, message } = await request.json() + + if (!subject || !message) { + return NextResponse.json({ error: 'Subject and message are required' }, { status: 400 }) + } + + // Insert contact request into database + const { error: insertError } = await supabase + .from('support_tickets') + .insert({ + user_id: user.id, + type: 'contact', + subject, + message, + status: 'open', + }) + + if (insertError) { + console.error('Error creating support ticket:', insertError) + return NextResponse.json({ error: 'Failed to submit contact request' }, { status: 500 }) + } + + return NextResponse.json({ success: true, message: 'Contact request submitted successfully' }) + } catch (error) { + console.error('Error in contact API:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/app/protected/help/page.tsx b/app/protected/help/page.tsx index eda45c57..306fa8ea 100644 --- a/app/protected/help/page.tsx +++ b/app/protected/help/page.tsx @@ -1,10 +1,13 @@ 'use client' -import React, { useState } from 'react' +import React, { useState, useEffect, useCallback } from 'react' +import Link from 'next/link' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' +import { Skeleton } from '@/components/ui/skeleton' +import { toast } from 'sonner' import { Search, HelpCircle, @@ -20,14 +23,28 @@ import { AlertCircle, CheckCircle, Clock, - ExternalLink + ExternalLink, + Bug, + ChevronRight, + Lightbulb, + X, + ChevronsDown, + ChevronsUp } from 'lucide-react' const quickActions = [ { icon: Shield, title: 'Reset Password', description: 'Change your account password', href: '/protected/settings' }, { icon: Users, title: 'Update Profile', description: 'Edit your profile information', href: '/protected/profile/view' }, { icon: Mail, title: 'Contact Support', description: 'Get help from our team', action: 'contact' }, - { icon: AlertCircle, title: 'Report a Bug', description: 'Help us improve the platform', action: 'bug' }, + { icon: Bug, title: 'Report a Bug', description: 'Help us improve the platform', action: 'bug' }, +] + +const popularTopics = [ + 'How do I reset my password?', + 'How do I enroll in a course?', + 'How do I send a message?', + 'Where can I see my test results?', + 'How do I add connections?', ] const faqCategories = [ @@ -161,25 +178,269 @@ const faqCategories = [ export default function HelpPage() { const [searchQuery, setSearchQuery] = useState('') + const [debouncedQuery, setDebouncedQuery] = useState('') const [filteredFaqs, setFilteredFaqs] = useState(faqCategories) + const [resultsCount, setResultsCount] = useState(0) + const [showContactForm, setShowContactForm] = useState(false) + const [showBugForm, setShowBugForm] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const [expandedItems, setExpandedItems] = useState([]) + + // Contact form state + const [contactSubject, setContactSubject] = useState('') + const [contactMessage, setContactMessage] = useState('') + const [contactSubmitting, setContactSubmitting] = useState(false) + const [contactErrors, setContactErrors] = useState({ subject: '', message: '' }) + + // Bug form state + const [bugTitle, setBugTitle] = useState('') + const [bugDescription, setBugDescription] = useState('') + const [bugSubmitting, setBugSubmitting] = useState(false) + const [bugErrors, setBugErrors] = useState({ title: '', description: '' }) - const handleSearch = (query: string) => { - setSearchQuery(query) - - if (!query.trim()) { + // Simulate initial loading + useEffect(() => { + const timer = setTimeout(() => setIsLoading(false), 500) + return () => clearTimeout(timer) + }, []) + + // Debounce search query + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedQuery(searchQuery) + }, 300) + return () => clearTimeout(timer) + }, [searchQuery]) + + // Handle ESC key for dialogs + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + if (showContactForm) setShowContactForm(false) + if (showBugForm) setShowBugForm(false) + } + } + window.addEventListener('keydown', handleEscape) + return () => window.removeEventListener('keydown', handleEscape) + }, [showContactForm, showBugForm]) + + // Perform search with debounced query + useEffect(() => { + if (!debouncedQuery.trim()) { setFilteredFaqs(faqCategories) + setResultsCount(0) return } const filtered = faqCategories.map(category => ({ ...category, faqs: category.faqs.filter(faq => - faq.question.toLowerCase().includes(query.toLowerCase()) || - faq.answer.toLowerCase().includes(query.toLowerCase()) + faq.question.toLowerCase().includes(debouncedQuery.toLowerCase()) || + faq.answer.toLowerCase().includes(debouncedQuery.toLowerCase()) ) })).filter(category => category.faqs.length > 0) + const count = filtered.reduce((acc, category) => acc + category.faqs.length, 0) + setResultsCount(count) setFilteredFaqs(filtered) + }, [debouncedQuery]) + + const clearSearch = () => { + setSearchQuery('') + setDebouncedQuery('') + } + + const expandAll = useCallback(() => { + const allItems: string[] = [] + filteredFaqs.forEach((category, catIndex) => { + category.faqs.forEach((_, faqIndex) => { + allItems.push(`item-${catIndex}-${faqIndex}`) + }) + }) + setExpandedItems(allItems) + }, [filteredFaqs]) + + const collapseAll = () => { + setExpandedItems([]) + } + + const highlightText = (text: string, query: string) => { + if (!query.trim()) return text + + const parts = text.split(new RegExp(`(${query})`, 'gi')) + return ( + + {parts.map((part, index) => + part.toLowerCase() === query.toLowerCase() + ? {part} + : {part} + )} + + ) + } + + const handleQuickAction = (action: string) => { + if (action === 'contact') { + setShowContactForm(true) + } else if (action === 'bug') { + setShowBugForm(true) + } + } + + const handleContactSubmit = async () => { + // Validate form + const errors = { subject: '', message: '' } + let hasErrors = false + + if (!contactSubject.trim()) { + errors.subject = 'Subject is required' + hasErrors = true + } else if (contactSubject.length < 5) { + errors.subject = 'Subject must be at least 5 characters' + hasErrors = true + } + + if (!contactMessage.trim()) { + errors.message = 'Message is required' + hasErrors = true + } else if (contactMessage.length < 20) { + errors.message = 'Message must be at least 20 characters' + hasErrors = true + } + + setContactErrors(errors) + if (hasErrors) return + + setContactSubmitting(true) + + try { + const response = await fetch('/api/support/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + subject: contactSubject, + message: contactMessage, + }), + }) + + if (response.ok) { + toast.success("Message sent successfully!", { + description: "We'll get back to you within 24-48 hours.", + }) + setContactSubject('') + setContactMessage('') + setContactErrors({ subject: '', message: '' }) + setShowContactForm(false) + } else { + const error = await response.json() + toast.error("Failed to send message", { + description: error.error || 'Please try again later.', + }) + } + } catch (error) { + console.error('Error sending contact message:', error) + toast.error("Failed to send message", { + description: 'Please check your connection and try again.', + }) + } finally { + setContactSubmitting(false) + } + } + + const handleBugSubmit = async () => { + // Validate form + const errors = { title: '', description: '' } + let hasErrors = false + + if (!bugTitle.trim()) { + errors.title = 'Bug title is required' + hasErrors = true + } else if (bugTitle.length < 10) { + errors.title = 'Title must be at least 10 characters' + hasErrors = true + } + + if (!bugDescription.trim()) { + errors.description = 'Description is required' + hasErrors = true + } else if (bugDescription.length < 30) { + errors.description = 'Description must be at least 30 characters' + hasErrors = true + } + + setBugErrors(errors) + if (hasErrors) return + + setBugSubmitting(true) + + try { + const response = await fetch('/api/support/bug-report', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: bugTitle, + description: bugDescription, + }), + }) + + if (response.ok) { + toast.success("Bug report submitted!", { + description: "Thank you for helping us improve the platform.", + }) + setBugTitle('') + setBugDescription('') + setBugErrors({ title: '', description: '' }) + setShowBugForm(false) + } else { + const error = await response.json() + toast.error("Failed to submit bug report", { + description: error.error || 'Please try again later.', + }) + } + } catch (error) { + console.error('Error submitting bug report:', error) + toast.error("Failed to submit bug report", { + description: 'Please check your connection and try again.', + }) + } finally { + setBugSubmitting(false) + } + } + + if (isLoading) { + return ( +
+ {/* Header Skeleton */} +
+
+
+ + +
+ +
+
+ + {/* Content Skeleton */} +
+
+ + +
+ {[1, 2, 3, 4].map((i) => ( + + ))} +
+ +
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+
+
+ ) } return ( @@ -208,9 +469,18 @@ export default function HelpPage() { handleSearch(e.target.value)} - className="pl-10 h-12 bg-zinc-900 border-zinc-800 text-white placeholder:text-zinc-500 focus:border-blue-500 focus:ring-blue-500" + onChange={(e) => setSearchQuery(e.target.value)} + className="pl-10 pr-10 h-12 bg-zinc-900 border-zinc-800 text-white placeholder:text-zinc-500 focus:border-blue-500 focus:ring-blue-500" /> + {searchQuery && ( + + )} {/* Quick Actions */} @@ -218,39 +488,123 @@ export default function HelpPage() {

Quick Actions

- {quickActions.map((action, index) => ( - - -
-
- -
-
- {action.title} - - {action.description} - -
-
-
-
- ))} + {quickActions.map((action, index) => { + if (action.href) { + return ( + + + +
+
+
+ +
+
+ {action.title} + + {action.description} + +
+
+ +
+
+
+ + ) + } + + return ( +
handleQuickAction(action.action || '')}> + + +
+
+
+ +
+
+ {action.title} + + {action.description} + +
+
+ +
+
+
+
+ ) + })}
)} {/* FAQ Categories */}
-

- {searchQuery ? 'Search Results' : 'Frequently Asked Questions'} -

+
+

+ {debouncedQuery ? 'Search Results' : 'Frequently Asked Questions'} +

+
+ {debouncedQuery && resultsCount > 0 && ( + + Found {resultsCount} result{resultsCount !== 1 ? 's' : ''} for "{debouncedQuery}" + + )} + {filteredFaqs.length > 0 && ( +
+ + +
+ )} +
+
{filteredFaqs.length === 0 ? ( - - -

No results found

-

Try different keywords or contact support

+ +
+ +
+
+

No results found for "{searchQuery}"

+

Try different keywords or browse popular topics

+
+
+
+ +

Popular Topics:

+
+
+ {popularTopics.map((topic, index) => ( + + ))} +
+
) : ( @@ -266,7 +620,12 @@ export default function HelpPage() {
- + {category.faqs.map((faq, faqIndex) => ( - {faq.question} + {highlightText(faq.question, debouncedQuery)} - {faq.answer} + {highlightText(faq.answer, debouncedQuery)} ))} @@ -289,8 +648,8 @@ export default function HelpPage() { )} - {/* Contact Support Section */} - {!searchQuery && ( + {/* Support Channels Section */} + {!debouncedQuery && ( @@ -298,30 +657,74 @@ export default function HelpPage() { Still Need Help? - Our support team is here to assist you + Choose the best way to reach our support team -
- -
-

Email Support

-

support@codeunia.com

-

Response time: 24-48 hours

+ {/* Live Chat */} +
+
+
+ +
+
+
+

Live Chat

+ + Available Now + +
+

Get instant help from our support team

+

Average response time: 2-5 minutes

+
+
-
- -
-

Support Hours

-

Monday - Friday: 9:00 AM - 6:00 PM IST

-

Saturday: 10:00 AM - 4:00 PM IST

+ + {/* Email Support */} +
+
+
+ +
+
+

Email Support

+ + support@codeunia.com + +

Response time: 24-48 hours

+
+
+
+ + {/* Support Hours */} +
+
+
+ +
+
+

Support Hours

+

Monday - Friday: 9:00 AM - 6:00 PM IST

+

Saturday: 10:00 AM - 4:00 PM IST

+

Closed on Sundays and public holidays

+
- )} @@ -351,6 +754,154 @@ export default function HelpPage() { )}
+ + {/* Contact Support Dialog */} + {showContactForm && ( +
setShowContactForm(false)}> + e.stopPropagation()}> + + + + Contact Support + + + Send us a message and we'll get back to you soon + + + +
+ + { + setContactSubject(e.target.value) + if (contactErrors.subject) setContactErrors({ ...contactErrors, subject: '' }) + }} + disabled={contactSubmitting} + className={`bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500 ${contactErrors.subject ? 'border-red-500' : ''}`} + /> + {contactErrors.subject && ( +

{contactErrors.subject}

+ )} +
+
+
+ + {contactMessage.length}/500 +
+