diff --git a/src/components/back-to-top.tsx b/src/components/back-to-top.tsx
new file mode 100644
index 0000000..0933b43
--- /dev/null
+++ b/src/components/back-to-top.tsx
@@ -0,0 +1,58 @@
+"use client";
+import React, { useState, useEffect } from "react";
+import { ChevronUp } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface BackToTopProps {
+ className?: string;
+ threshold?: number;
+}
+
+export default function BackToTop({
+ className,
+ threshold = 300
+}: BackToTopProps) {
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const toggleVisibility = () => {
+ if (window.pageYOffset > threshold) {
+ setIsVisible(true);
+ } else {
+ setIsVisible(false);
+ }
+ };
+
+ window.addEventListener("scroll", toggleVisibility);
+ return () => window.removeEventListener("scroll", toggleVisibility);
+ }, [threshold]);
+
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: "smooth",
+ });
+ };
+
+ if (!isVisible) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
index 278ce1a..562c687 100644
--- a/src/components/footer.tsx
+++ b/src/components/footer.tsx
@@ -1,3 +1,191 @@
+"use client";
+import React, { useState } from 'react';
+import Link from 'next/link';
+import { Mail, Facebook, X, Instagram, Linkedin, Send } from 'lucide-react';
+
+export default function Footer() {
+ const [email, setEmail] = useState('');
+ const [isSubscribed, setIsSubscribed] = useState(false);
+
+ const handleNewsletterSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (email) {
+ setIsSubscribed(true);
+ setEmail('');
+ // Here you would typically send the email to your backend
+ setTimeout(() => setIsSubscribed(false), 3000);
+ }
+ };
+
+ const quickLinks = [
+ { name: 'Home', href: '/' },
+ { name: 'AI Generator', href: '/ai-generator' },
+ { name: 'Dashboard', href: '/dashboard' },
+ { name: 'Features', href: '/#features' },
+ { name: 'Sign Up', href: '/sign' },
+ ];
+
+ const helpLinks = [
+ { name: 'How it Works', href: '#how-it-works' },
+ { name: 'FAQ', href: '#faq' },
+ { name: 'Contact Support', href: '#contact' },
+ { name: 'Documentation', href: '#docs' },
+ { name: 'Pricing', href: '#pricing' },
+ ];
+
+ const supportLinks = [
+ { name: 'Terms & Conditions', href: '/terms' },
+ { name: 'Privacy Policy', href: '/privacy' },
+ ];
+
+ const socialLinks = [
+ { name: 'Facebook', icon: Facebook, href: 'https://facebook.com' },
+ { name: 'X', icon: X, href: 'https://x.com' },
+ { name: 'Instagram', icon: Instagram, href: 'https://instagram.com' },
+ { name: 'LinkedIn', icon: Linkedin, href: 'https://linkedin.com' },
+ ];
+
+ return (
+
+ {/* Aurora effect overlay */}
+
+
+
+
+ {/* Logo and Info Section */}
+
+
+
+
AsHelp
+
+
+ Get professional assignment writing services from verified experts.
+ 100% AI-free, plagiarism-free content delivered on time.
+
+
+ {/* Newsletter Subscription */}
+
+
Stay Updated
+
+ {isSubscribed && (
+
Thank you for subscribing!
+ )}
+
+
+
+ {/* Quick Links Section */}
+
+
Quick Links
+
+ {quickLinks.map((link, index) => (
+
+
+ {link.name}
+
+
+ ))}
+
+
+
+ {/* Help Section */}
+
+
+ {/* Support & Social Section */}
+
+
Support
+
+ {supportLinks.map((link, index) => (
+
+
+ {link.name}
+
+
+ ))}
+
+
+ {/* Social Media Icons */}
+
+
Follow Us
+
+ {socialLinks.map((social, index) => {
+ const IconComponent = social.icon;
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+
+ {/* Bottom Copyright Section */}
+
+
+
+ Copyright © {new Date().getFullYear()} AsHelp. All rights reserved.
+
+
+ Made with ❤️ for students worldwide
+
+
+
import React from 'react';
import { Twitter, Facebook, Linkedin } from 'lucide-react';
@@ -42,6 +230,7 @@ const Footer = () => {
);
+}
};
-export default Footer;
\ No newline at end of file
+export default Footer;
diff --git a/src/components/mwrap.tsx b/src/components/mwrap.tsx
index 4909d60..d0d2123 100644
--- a/src/components/mwrap.tsx
+++ b/src/components/mwrap.tsx
@@ -10,7 +10,6 @@ interface TestimonialCard {
}
const testimonials: TestimonialCard[] = [
-
{
"id": 1,
"name": "Hitesh",
@@ -66,6 +65,34 @@ const testimonials: TestimonialCard[] = [
"username": "@anst",
"content": "Lowkey didn't think they'd pull it off but they nailed it.",
"avatar": "https://avatar.vercel.sh/james"
+ },
+ {
+ "id": 9,
+ "name": "Rahul",
+ "username": "@rahul",
+ "content": "Best service ever! Got my assignment done in time with amazing quality.",
+ "avatar": "https://avatar.vercel.sh/rahul"
+ },
+ {
+ "id": 10,
+ "name": "Priya",
+ "username": "@priya",
+ "content": "Professional work, delivered exactly what I needed. Highly recommended!",
+ "avatar": "https://avatar.vercel.sh/priya"
+ },
+ {
+ "id": 11,
+ "name": "Arjun",
+ "username": "@arjun",
+ "content": "Saved my grades with their excellent assignment help. Thank you!",
+ "avatar": "https://avatar.vercel.sh/arjun"
+ },
+ {
+ "id": 12,
+ "name": "Kavya",
+ "username": "@kavya",
+ "content": "Fast delivery and top-notch quality. Will definitely use again.",
+ "avatar": "https://avatar.vercel.sh/kavya"
}
];
@@ -89,14 +116,14 @@ const TestimonialCard: React.FC<{ testimonial: TestimonialCard }> = ({ testimoni
};
const TestimonialMarquee: React.FC = () => {
- const firstRow = testimonials.slice(0, Math.ceil(testimonials.length / 2));
- const secondRow = testimonials.slice(Math.ceil(testimonials.length / 2));
+ // Duplicate testimonials to ensure continuous scrolling without empty slots
+ const extendedTestimonials = [...testimonials, ...testimonials, ...testimonials];
+ const firstRow = extendedTestimonials.slice(0, Math.ceil(extendedTestimonials.length / 2));
+ const secondRow = extendedTestimonials.slice(Math.ceil(extendedTestimonials.length / 2));
return (
-
-
-
+
{/* First row - left to right */}
{
pauseOnHover={true}
className="overflow-hidden"
>
- {firstRow.map((testimonial) => (
-
+ {firstRow.map((testimonial, index) => (
+
))}
@@ -118,8 +145,8 @@ const TestimonialMarquee: React.FC = () => {
pauseOnHover={true}
className="overflow-hidden"
>
- {secondRow.map((testimonial) => (
-
+ {secondRow.map((testimonial, index) => (
+
))}
diff --git a/src/components/nav.tsx b/src/components/nav.tsx
index 579dfd6..9ec0731 100644
--- a/src/components/nav.tsx
+++ b/src/components/nav.tsx
@@ -15,20 +15,25 @@ import { GitHubStarsButton } from '@/components/animate-ui/buttons/github-stars'
import { useRouter } from "next/navigation";
import { supabase } from "@/lib/supabaseclient";
import type { User } from '@supabase/supabase-js';
+import Link from "next/link";
export function NavbarDemo() {
const navItems = [
{
name: "Features",
- link: "",
+ link: "/#features",
},
{
name: "AI Generator",
link: "/ai-generator",
},
+ {
+ name: "Dashboard",
+ link: "/dashboard",
+ },
{
name: "Contact",
- link: "",
+ link: "/contact",
},
];
@@ -37,13 +42,15 @@ export function NavbarDemo() {
const router = useRouter();
useEffect(() => {
+ if (!supabase) return;
+
const getUser = async () => {
- const { data: { user } } = await supabase.auth.getUser();
+ const { data: { user } } = await supabase!.auth.getUser();
setUser(user);
};
getUser();
// Listen for auth state changes
- const { data: listener } = supabase.auth.onAuthStateChange((_event, session) => {
+ const { data: listener } = supabase!.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => {
@@ -57,7 +64,9 @@ export function NavbarDemo() {
{/* Desktop Navigation */}
-
+
+
+
{user === null && (
@@ -81,14 +90,14 @@ export function NavbarDemo() {
onClose={() => setIsMobileMenuOpen(false)}
>
{navItems.map((item, idx) => (
-
setIsMobileMenuOpen(false)}
className="relative text-black dark:text-black"
>
{item.name}
-
+
))}
{user === null && (
diff --git a/src/components/signup.tsx b/src/components/signup.tsx
index 1660df1..3a9c94d 100644
--- a/src/components/signup.tsx
+++ b/src/components/signup.tsx
@@ -21,6 +21,12 @@ export default function SignupFormDemo() {
const handleSubmit = async (e: React.FormEvent
) => {
e.preventDefault();
setErrorMsg("");
+
+ if (!supabase) {
+ setErrorMsg("Authentication service is not available. Please check your configuration.");
+ return;
+ }
+
setPasswordMatchError("");
try {
if (isSignUp) {
@@ -50,6 +56,11 @@ export default function SignupFormDemo() {
};
const handleGoogleSignIn = async () => {
+ if (!supabase) {
+ setErrorMsg("Authentication service is not available. Please check your configuration.");
+ return;
+ }
+
try {
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
diff --git a/src/components/ui/resizable-navbar.tsx b/src/components/ui/resizable-navbar.tsx
index d24f07b..a8a81c7 100644
--- a/src/components/ui/resizable-navbar.tsx
+++ b/src/components/ui/resizable-navbar.tsx
@@ -113,6 +113,7 @@ export const NavItems = ({ items, className, onItemClick }: NavItemsProps) => {
setHovered(null)}
className={cn(
+ "flex flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800",
// ✅ removed absolute/inset-0 — no more overlap
"hidden lg:flex flex-1 items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800",
className,
@@ -122,7 +123,7 @@ export const NavItems = ({ items, className, onItemClick }: NavItemsProps) => {
setHovered(idx)}
onClick={onItemClick}
- className="relative px-4 py-2 text-black dark:text-black"
+ className="relative px-4 py-2 text-black dark:text-black whitespace-nowrap"
key={`link-${idx}`}
href={item.link}
>
@@ -235,7 +236,7 @@ export const NavbarLogo = () => {
width={30}
height={30}
/>
- AssHelp
+ AsHelp
);
};
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index a597926..232199c 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -1,6 +1,7 @@
import * as React from "react"
import { cn } from "@/lib/utils"
+export type TextareaProps = React.TextareaHTMLAttributes
export type TextareaProps = React.TextareaHTMLAttributes;
const Textarea = React.forwardRef(
diff --git a/src/components/usage-tracker.tsx b/src/components/usage-tracker.tsx
index 7d680d4..30df52a 100644
--- a/src/components/usage-tracker.tsx
+++ b/src/components/usage-tracker.tsx
@@ -28,6 +28,20 @@ export function UsageTracker() {
}
}, []);
+ // const updateUsage = (type: 'generation' | 'export') => {
+ // setUsage(prev => {
+ // const newUsage = {
+ // ...prev,
+ // ...(type === 'generation'
+ // ? { geminiUsage: prev.geminiUsage + 1 }
+ // : { exportsToday: prev.exportsToday + 1 }
+ // )
+ // };
+ // localStorage.setItem('ai-generator-usage', JSON.stringify(newUsage));
+ // return newUsage;
+ // });
+ // };
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const updateUsage = (type: 'generation' | 'export') => {
setUsage(prev => {
diff --git a/src/lib/document-utils.ts b/src/lib/document-utils.ts
index a41c01c..1e1fdbc 100644
--- a/src/lib/document-utils.ts
+++ b/src/lib/document-utils.ts
@@ -20,6 +20,12 @@ const getImageAsBase64 = async (url: string): Promise => {
export const exportToPDF = async (content: string, filename: string = 'assignment') => {
try {
+ // Check if we're in browser environment
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
+ console.error('PDF export only works in browser environment');
+ return false;
+ }
+
const pdf = new jsPDF();
// Parse HTML and extract structured content
@@ -34,39 +40,36 @@ export const exportToPDF = async (content: string, filename: string = 'assignmen
// Process each element including images
const elements = tempDiv.querySelectorAll('h1, h2, h3, h4, h5, h6, p, div, li, img');
- for (const element of elements) {
+ for (const element of Array.from(elements)) {
+ const text = element.textContent?.trim() || '';
+ if (!text && element.tagName !== 'IMG') continue;
+
+ // Handle images
if (element.tagName === 'IMG') {
const img = element as HTMLImageElement;
- const downloadUrl = img.getAttribute('data-download-url') || img.src;
+ const imgUrl = img.src;
- try {
- const base64 = await getImageAsBase64(downloadUrl);
- if (base64) {
- // Check if we need a new page for the image
- if (yPosition + 60 > pageHeight - 20) {
- pdf.addPage();
- yPosition = 20;
+ if (imgUrl && (imgUrl.startsWith('http') || imgUrl.startsWith('data:'))) {
+ const base64 = await getImageAsBase64(imgUrl);
+ if (base64 && yPosition < pageHeight - 60) {
+ try {
+ pdf.addImage(base64, 'PNG', margin, yPosition, 150, 100);
+ yPosition += 110;
+ } catch (error) {
+ console.error('Failed to add image to PDF:', error);
}
-
- // Add image (scaled to fit)
- const imgWidth = 160;
- const imgHeight = 90;
- pdf.addImage(base64, 'JPEG', margin, yPosition, imgWidth, imgHeight);
- yPosition += imgHeight + 10;
}
- } catch (error) {
- console.error('Failed to add image to PDF:', error);
}
continue;
}
- const text = element.textContent?.trim();
- if (!text) continue;
+ // Get heading level
+ const headingMatch = element.tagName.match(/^H([1-6])$/);
+ const headingLevel = headingMatch ? parseInt(headingMatch[1]) : null;
- // Set font size based on element type with proper heading hierarchy
- const headingLevel = element.tagName.match(/^H([1-6])$/)?.[1];
+ // Set font size based on element type
if (headingLevel) {
- const fontSize = Math.max(20 - parseInt(headingLevel) * 2, 12);
+ const fontSize = Math.max(20 - parseInt(headingLevel.toString()) * 2, 12);
pdf.setFontSize(fontSize);
pdf.setFont('helvetica', 'bold');
} else {
@@ -106,12 +109,23 @@ export const exportToPDF = async (content: string, filename: string = 'assignmen
export const exportToWord = async (content: string, filename: string = 'assignment') => {
try {
+ // Check if we're in browser environment
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
+ console.error('Word export only works in browser environment');
+ return false;
+ }
+
// Parse HTML and create structured RTF
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content.replace(/