Update share card with fb, whatsapp, download and export date; update CBT and Doubt Forum UI
This commit is contained in:
@@ -18,6 +18,13 @@ export async function GET(req: NextRequest) {
|
|||||||
const target = searchParams.get('target') || 'JEE';
|
const target = searchParams.get('target') || 'JEE';
|
||||||
const plan = searchParams.get('plan') || 'Free';
|
const plan = searchParams.get('plan') || 'Free';
|
||||||
|
|
||||||
|
// Read date (if passed, otherwise use current date/time)
|
||||||
|
const exportDate = searchParams.get('date') || new Intl.DateTimeFormat('en-IN', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short',
|
||||||
|
timeZone: 'Asia/Kolkata',
|
||||||
|
}).format(new Date());
|
||||||
|
|
||||||
// Load logo from disk and convert to base64
|
// Load logo from disk and convert to base64
|
||||||
const logoPath = join(process.cwd(), 'public', 'PragatiVeer_Logo', 'PNG_Logos', 'Rectangular_dark_background.png');
|
const logoPath = join(process.cwd(), 'public', 'PragatiVeer_Logo', 'PNG_Logos', 'Rectangular_dark_background.png');
|
||||||
let logoUrl = '';
|
let logoUrl = '';
|
||||||
@@ -110,6 +117,7 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<div style={{ fontSize: 44, fontWeight: 900, color: '#ffffff', letterSpacing: '-0.02em' }}>{name}</div>
|
<div style={{ fontSize: 44, fontWeight: 900, color: '#ffffff', letterSpacing: '-0.02em' }}>{name}</div>
|
||||||
|
<div style={{ fontSize: 20, color: 'rgba(255,255,255,0.5)', marginTop: 4 }}>Generated: {exportDate}</div>
|
||||||
<div style={{ display: 'flex', marginTop: 8 }}>
|
<div style={{ display: 'flex', marginTop: 8 }}>
|
||||||
<div style={{ fontSize: 22, color: '#06b6d4', fontWeight: 700, marginRight: 16 }}>{`${target} Aspirant`}</div>
|
<div style={{ fontSize: 22, color: '#06b6d4', fontWeight: 700, marginRight: 16 }}>{`${target} Aspirant`}</div>
|
||||||
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>•</div>
|
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>•</div>
|
||||||
@@ -118,10 +126,10 @@ export async function GET(req: NextRequest) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '4px' }}>
|
||||||
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.4)', fontWeight: 600, letterSpacing: '0.05em' }}>
|
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.7)', fontWeight: 600 }}>pragativeer.com</div>
|
||||||
pragativeer.com
|
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>contact@pragativeer.com</div>
|
||||||
</div>
|
<div style={{ fontSize: 22, color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>+91 9079337961</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export default function CBTPage() {
|
|||||||
<span className="p-2.5 rounded-xl bg-yellow-50 dark:bg-[var(--cyan-ai-10)] border border-yellow-300/60 dark:border-[var(--cyan-ai-20)]">
|
<span className="p-2.5 rounded-xl bg-yellow-50 dark:bg-[var(--cyan-ai-10)] border border-yellow-300/60 dark:border-[var(--cyan-ai-20)]">
|
||||||
<CheckSquare className="text-yellow-600 dark:text-[var(--cyan-ai)]" size={28} />
|
<CheckSquare className="text-yellow-600 dark:text-[var(--cyan-ai)]" size={28} />
|
||||||
</span>
|
</span>
|
||||||
Computer Based Tests (CBT)
|
Mock Tests (CBT)
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
<p className="text-muted-foreground text-base font-medium max-w-2xl pl-1">
|
||||||
Real exam environment simulations to conquer {selectedPattern === "NEET" ? "NEET" : (targetExam || "JEE")}.
|
Real exam environment simulations to conquer {selectedPattern === "NEET" ? "NEET" : (targetExam || "JEE")}.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ReactNode, useState, useEffect, useRef } from "react"
|
import { ReactNode, useState, useEffect, useRef } from "react"
|
||||||
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare } from "lucide-react"
|
import { BookOpen, LayoutDashboard, Settings, FileText, Activity, LineChart, Trophy, User, ChevronUp, Lock, ShieldAlert, Flame, Target, Gauge, Zap, Calendar, Clock, BrainCircuit, BarChart3, School, CheckSquare, MessageSquare } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { LanguageToggle } from "@/components/LanguageToggle"
|
import { LanguageToggle } from "@/components/LanguageToggle"
|
||||||
@@ -145,7 +145,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
<NavItem
|
<NavItem
|
||||||
section="pro"
|
section="pro"
|
||||||
href="/dashboard/community"
|
href="/dashboard/community"
|
||||||
icon={<School size={17} />}
|
icon={<MessageSquare size={17} />}
|
||||||
label="Doubt Forum"
|
label="Doubt Forum"
|
||||||
active={pathname.startsWith("/dashboard/community")}
|
active={pathname.startsWith("/dashboard/community")}
|
||||||
isLocked={!hasAccessTo("doubt-resolution-community")}
|
isLocked={!hasAccessTo("doubt-resolution-community")}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRef, useState, useEffect } from "react"
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { BrainCircuit, Play, ChevronRight, Activity, Target, CheckCircle2, AlertTriangle, TrendingUp, Calendar, Zap, LayoutDashboard, Share2, Trophy, Lock, ArrowRight, Clock } from "lucide-react"
|
import { BrainCircuit, Play, ChevronRight, Activity, Target, CheckCircle2, AlertTriangle, TrendingUp, Calendar, Zap, LayoutDashboard, Share2, Trophy, Lock, ArrowRight, Clock, Facebook, Download, MessageCircle } from "lucide-react"
|
||||||
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
import { MistakeHeatmap } from "@/components/AI/MistakeHeatmap"
|
||||||
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
|
import { AdaptiveStudyPlan } from "@/components/AI/AdaptiveStudyPlan"
|
||||||
import { useActiveUser } from "@/hooks/useActiveUser"
|
import { useActiveUser } from "@/hooks/useActiveUser"
|
||||||
@@ -14,6 +14,7 @@ export default function DashboardPage() {
|
|||||||
const [userStats, setUserStats] = useState<any>(null)
|
const [userStats, setUserStats] = useState<any>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [downloading, setDownloading] = useState(false)
|
const [downloading, setDownloading] = useState(false)
|
||||||
|
const [shareMenuOpen, setShareMenuOpen] = useState(false)
|
||||||
const progressRef = useRef<HTMLDivElement>(null)
|
const progressRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,9 +40,7 @@ export default function DashboardPage() {
|
|||||||
fetchStats()
|
fetchStats()
|
||||||
}, [userLoading, user?.id, userId])
|
}, [userLoading, user?.id, userId])
|
||||||
|
|
||||||
const handleExport = async () => {
|
const getShareUrl = () => {
|
||||||
try {
|
|
||||||
setDownloading(true)
|
|
||||||
const score = isNew ? "0 / 0" : `${userStats?.today_score || 0} / ${userStats?.today_max_score || 60}`
|
const score = isNew ? "0 / 0" : `${userStats?.today_score || 0} / ${userStats?.today_max_score || 60}`
|
||||||
const accuracy = isNew ? "0%" : `${userStats?.overall_accuracy || 0}%`
|
const accuracy = isNew ? "0%" : `${userStats?.overall_accuracy || 0}%`
|
||||||
const qs = isNew ? "0" : (userStats?.total_questions_attempted || 0).toLocaleString()
|
const qs = isNew ? "0" : (userStats?.total_questions_attempted || 0).toLocaleString()
|
||||||
@@ -50,9 +49,19 @@ export default function DashboardPage() {
|
|||||||
const target = user?.target_exam === "NEET" ? "NEET" : "JEE"
|
const target = user?.target_exam === "NEET" ? "NEET" : "JEE"
|
||||||
const plan = user?.tier === "Al_Coach" ? "AI Coach" : user?.tier || "Free"
|
const plan = user?.tier === "Al_Coach" ? "AI Coach" : user?.tier || "Free"
|
||||||
const name = user?.name || "Student"
|
const name = user?.name || "Student"
|
||||||
|
const date = new Intl.DateTimeFormat('en-IN', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short',
|
||||||
|
timeZone: 'Asia/Kolkata',
|
||||||
|
}).format(new Date());
|
||||||
|
|
||||||
const url = `/api/og/dashboard?score=${encodeURIComponent(score)}&accuracy=${encodeURIComponent(accuracy)}&qs=${encodeURIComponent(qs)}&rank=${encodeURIComponent(rank)}&name=${encodeURIComponent(name)}&target=${target}&plan=${encodeURIComponent(plan)}`
|
return `/api/og/dashboard?score=${encodeURIComponent(score)}&accuracy=${encodeURIComponent(accuracy)}&qs=${encodeURIComponent(qs)}&rank=${encodeURIComponent(rank)}&name=${encodeURIComponent(name)}&target=${target}&plan=${encodeURIComponent(plan)}&date=${encodeURIComponent(date)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
setDownloading(true)
|
||||||
|
const url = getShareUrl()
|
||||||
const res = await fetch(url)
|
const res = await fetch(url)
|
||||||
const blob = await res.blob()
|
const blob = await res.blob()
|
||||||
const blobUrl = window.URL.createObjectURL(blob)
|
const blobUrl = window.URL.createObjectURL(blob)
|
||||||
@@ -68,9 +77,23 @@ export default function DashboardPage() {
|
|||||||
console.error("Failed to export image", err)
|
console.error("Failed to export image", err)
|
||||||
} finally {
|
} finally {
|
||||||
setDownloading(false)
|
setDownloading(false)
|
||||||
|
setShareMenuOpen(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleShareFacebook = () => {
|
||||||
|
const fullUrl = window.location.origin + getShareUrl()
|
||||||
|
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(fullUrl)}`, '_blank')
|
||||||
|
setShareMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShareWhatsApp = () => {
|
||||||
|
const fullUrl = window.location.origin + getShareUrl()
|
||||||
|
const text = `Check out my performance on PragatiVeer!\n\n${fullUrl}`
|
||||||
|
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank')
|
||||||
|
setShareMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
|
<div className="h-[70vh] flex flex-col items-center justify-center p-8">
|
||||||
@@ -100,10 +123,28 @@ export default function DashboardPage() {
|
|||||||
Your overall progress, scores, and active study plan at a glance.
|
Your overall progress, scores, and active study plan at a glance.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleExport} variant="outline" size="sm" className="gap-2 font-medium shrink-0" disabled={downloading}>
|
<div className="relative">
|
||||||
|
<Button onClick={() => setShareMenuOpen(!shareMenuOpen)} variant="outline" size="sm" className="gap-2 font-medium shrink-0" disabled={downloading}>
|
||||||
{downloading ? <div className="h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin"></div> : <Share2 size={16} />}
|
{downloading ? <div className="h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin"></div> : <Share2 size={16} />}
|
||||||
{downloading ? 'Generating...' : 'Share Card'}
|
{downloading ? 'Generating...' : 'Share Card'}
|
||||||
</Button>
|
</Button>
|
||||||
|
{shareMenuOpen && (
|
||||||
|
<div className="absolute right-0 top-full mt-2 w-48 bg-card border border-border rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200">
|
||||||
|
<button onClick={handleShareFacebook} className="flex items-center w-full px-4 py-3 text-sm hover:bg-secondary text-left transition-colors">
|
||||||
|
<Facebook size={16} className="mr-3 text-blue-600" />
|
||||||
|
Share on Facebook
|
||||||
|
</button>
|
||||||
|
<button onClick={handleShareWhatsApp} className="flex items-center w-full px-4 py-3 text-sm hover:bg-secondary text-left transition-colors">
|
||||||
|
<MessageCircle size={16} className="mr-3 text-green-500" />
|
||||||
|
Share on WhatsApp
|
||||||
|
</button>
|
||||||
|
<button onClick={handleDownload} className="flex items-center w-full px-4 py-3 text-sm hover:bg-secondary text-left transition-colors border-t border-border">
|
||||||
|
<Download size={16} className="mr-3 text-primary" />
|
||||||
|
Download Image
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref={progressRef} className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 -mx-4 bg-background rounded-2xl">
|
<div ref={progressRef} className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 -mx-4 bg-background rounded-2xl">
|
||||||
{/* Today's Score */}
|
{/* Today's Score */}
|
||||||
|
|||||||
Reference in New Issue
Block a user