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
28 changes: 7 additions & 21 deletions src/app/(root)/(chat)/_components/chatinput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import TypewriterPlaceholder from './typewriter';
import { Upload, Stars, Loader2 } from 'lucide-react';
import { Upload, Stars, Loader2, MoveUp } from 'lucide-react';
import { getUser } from '@/actions/user_actions';
import { useRouter } from 'next/navigation';
import { useAppDispatch } from '@/redux/hooks';
Expand Down Expand Up @@ -155,13 +155,13 @@ const ChatInput = ({ isLoggedIn, onSubmit, setShowResumePopup }: {
<div className="w-full mx-auto z-10 px-4 animate-fade-in">
{/* Form Container */}
<form onSubmit={handleFormSubmit} className="w-full">
<div className="relative shadow-sm border border-[#ffffff1a] bg-[#171717cc] backdrop-blur rounded-lg p-4 transition-all duration-300 hover:border-[#ffffff33]">
<div className="relative shadow-sm bg-[hsla(0,0%,60%,0.12)] backdrop-blur rounded-[20px] p-4 transition-all duration-300">
<div className="absolute inset-0 overflow-hidden rounded-lg pointer-events-none">
<svg className="absolute bottom-0 right-0 w-full h-full">
<svg className="absolute bottom-0 rounded-[20px] right-0 w-full h-full">
<defs>
<linearGradient id="line-gradient" x1="70%" y1="70%" x2="100%" y2="100%">
<linearGradient id="line-gradient" x1="50%" y1="70%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#53ffe9d9" stopOpacity="0%" />
<stop offset="40%" stopColor="#53ffe9d9" stopOpacity="80%" />
<stop offset="40%" stopColor="#53ffe9d9" stopOpacity="60%" />
<stop offset="50%" stopColor="#53ffe9d9" stopOpacity="80%" />
<stop offset="100%" stopColor="#53ffe9d9" stopOpacity="0%" />
</linearGradient>
Expand Down Expand Up @@ -205,28 +205,14 @@ const ChatInput = ({ isLoggedIn, onSubmit, setShowResumePopup }: {
{/* Action Buttons */}
<div className="flex justify-between text-sm pt-2">
<div className="flex gap-2 items-center">
<button
className="flex items-center text-gray-400 hover:text-[#53ffe9d9] rounded-md p-1 transition-colors duration-200"
onClick={() => {/* Handle upload */}}
type="button"
>
<Upload className="text-xl" />
</button>
<button
className="flex items-center text-gray-400 opacity-30 cursor-not-allowed rounded-md p-1"
type="button"
disabled
>
<Stars className="text-xl" />
</button>
</div>
<button
className="px-4 py-1 bg-[#53ffe9d9] text-black font-medium rounded-md hover:bg-[#53ffe9] transition-colors"
className="px-4 py-1 text-gray-700 font-medium rounded-md hover:bg-[#53ffe9] transition-colors"
type="button"
disabled={isLoading}
onClick={handleSubmit}
>
{isLoading ? <Loader2 className="animate-spin" /> : 'Send'}
{isLoading ? <Loader2 className="animate-spin" /> : <MoveUp className="w-6 h-6"/>}
</button>
</div>
</div>
Expand Down
265 changes: 137 additions & 128 deletions src/app/(root)/(chat)/_components/heroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,42 @@
'use client';

import React, { useRef, useState, useEffect } from 'react';
import Image from 'next/image';
import { FaPaperclip, FaUpload, FaGlobe, FaTwitter, FaLinkedin, FaGithub } from 'react-icons/fa';
import { useGoogleLogin } from '@react-oauth/google';
import { SparklesIcon, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useAppDispatch } from '@/redux/hooks';
import axios from 'axios';
import { userData } from '@/redux/slices/userSlice';
import toast from 'react-hot-toast';
import { Loader2, SparklesIcon } from 'lucide-react';
import JobSearchResults from './job-result';
import QueryLimitPopup from './QueryLimitPopup';
import ResumeUploadPopup from './ResumeUploadPopup'; // Import the new component
import ChatInput from './chatinput';
import { Button } from '@/components/ui/button';
import { fetchJobs } from "@/actions/chat_actions"
import QueryLimitPopup from './QueryLimitPopup';
import ResumeUploadPopup from './ResumeUploadPopup';
import JobSearchResults from './job-result';
import { fetchJobs } from "@/actions/chat_actions";

const QUERY_LIMIT = 5678;
const STORAGE_KEY = 'userQueryCount';

const HeroSection = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
const inputRef = useRef<HTMLTextAreaElement>(null);
const dispatch = useAppDispatch();

const [isLoading, setIsLoading] = useState(false);
const [query, setQuery] = useState('');
const [showResults, setShowResults] = useState(false);
const [queryCount, setQueryCount] = useState(0);
const [showPopup, setShowPopup] = useState(false); // State to control query limit popup
const [showResumePopup, setShowResumePopup] = useState(false); // New state for resume popup
const [jobApiData, setJobApiData] = useState<any>(null)
const [isApiLoading, setIsApiLoading] = useState(false)
const [showPopup, setShowPopup] = useState(false);
const [showResumePopup, setShowResumePopup] = useState(false);
const [jobApiData, setJobApiData] = useState<any>(null);
const [isApiLoading, setIsApiLoading] = useState(false);

useEffect(() => {
const storedCount = localStorage.getItem(STORAGE_KEY);
if (storedCount) {
setQueryCount(parseInt(storedCount, 10));
}
if (storedCount) setQueryCount(parseInt(storedCount, 10));
}, []);

console.log("Results state:", showResults);

useEffect(() => {
localStorage.setItem(STORAGE_KEY, queryCount.toString());
}, [queryCount]);

const handleCloseResults = () => {
console.log("Closing job results");
setShowResults(false);
setQuery('');
setJobApiData(null);
Expand All @@ -58,140 +51,156 @@ const HeroSection = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
};

const handleChatSubmit = async (inputText: string) => {
console.log("Chat submit triggered with text:", inputText);
setQuery(inputText);
setIsApiLoading(true);

try {
console.log("Calling fetchJobs with:", inputText);
// Call the API with the chat input
const data = await fetchJobs(inputText);
console.log("Job data received in component:", data);
setJobApiData(data);
} catch (error) {
console.error("Error fetching job data:", error);
toast.error("Failed to fetch job results");
} finally {
console.log("API loading completed");
setIsApiLoading(false);
setShowResults(true);
}

setQueryCount(prevCount => {
const newCount = prevCount + 1;
console.log("New query count:", newCount);
if (newCount >= QUERY_LIMIT) {
setShowPopup(true);
}

setQueryCount(prev => {
const newCount = prev + 1;
if (newCount >= QUERY_LIMIT) setShowPopup(true);
return newCount;
});
};

return (
<div className="min-h-screen flex flex-col">
{/* Ray Container with multiple light rays */}
{/* Background Light Rays */}
<div
className="fixed inset-0 pointer-events-none select-none"
style={{
'--gradient-opacity': '.85',
'--ray-gradient': 'radial-gradient(rgb(83 255 233 / 85%) 0%, rgba(43, 166, 255, 0) 100%)',
transition: 'opacity 0.25s linear',
} as React.CSSProperties}
>
{/* Light Rays (existing code) */}
<div className="absolute rounded-full"
style={{
background: 'var(--ray-gradient)',
width: '480px',
height: '680px',
transform: 'rotate(80deg)',
top: '-540px',
left: '250px',
filter: 'blur(110px)'
}}
/>
{/* Other light rays... */}
className="fixed inset-0 pointer-events-none select-none"
style={{
'--gradient-opacity': '.85',
'--ray-gradient': 'radial-gradient(rgb(83 255 233 / 85%) 0%, rgba(43, 166, 255, 0) 100%)',
transition: 'opacity 0.25s linear',
} as React.CSSProperties}
>
{/* Light Ray 1 */}
<div className="absolute rounded-full" style={{
background: 'var(--ray-gradient)',
width: '480px',
height: '680px',
transform: 'rotate(80deg)',
top: '-540px',
left: '250px',
filter: 'blur(110px)'
}} />
{/* Light Ray 2 */}
<div className="absolute rounded-full" style={{
background: 'var(--ray-gradient)',
width: '110px',
height: '400px',
transform: 'rotate(-20deg)',
top: '-280px',
left: '350px',
mixBlendMode: 'overlay',
opacity: '0.6',
filter: 'blur(60px)'
}} />
{/* Light Ray 3 */}
<div className="absolute rounded-full" style={{
background: 'var(--ray-gradient)',
width: '400px',
height: '370px',
transform: 'rotate(95deg)',
top: '-350px',
left: '200px',
mixBlendMode: 'overlay',
opacity: '0.6',
filter: 'blur(21px)'
}} />
{/* Light Ray 4 */}
<div className="absolute rounded-full" style={{
background: 'var(--ray-gradient)',
width: '330px',
height: '370px',
transform: 'rotate(75deg)',
top: '-330px',
left: '50px',
mixBlendMode: 'overlay',
opacity: '0.5',
filter: 'blur(21px)'
}} />
{/* Light Ray 5 */}
<div className="absolute rounded-full" style={{
background: 'var(--ray-gradient)',
width: '110px',
height: '400px',
transform: 'rotate(-40deg)',
top: '-280px',
left: '-10px',
mixBlendMode: 'overlay',
opacity: '0.8',
filter: 'blur(60px)'
}} />
</div>

{/* Conditional Rendering for Hero Content or Job Search Results */}
{showResults && queryCount < QUERY_LIMIT ? (
<>
<JobSearchResults
query={query}
onClose={handleCloseResults}
apiData={jobApiData}
isLoading={isApiLoading}
/>
{/* Chat input below job results */}
<div className='w-[60%] mx-auto mt-4'>
<ChatInput
isLoggedIn={isLoggedIn}
onSubmit={handleChatSubmit}
setShowResumePopup={setShowResumePopup} // Pass the first signup handler
{/* Main Content */}
<div className="flex-1 flex flex-col items-center text-center mt-2 px-4">
{!showResults || queryCount >= QUERY_LIMIT ? (
<>
<div className="flex justify-center mt-4 mb-[10%]">
<Button variant="outline" className="rounded-full bg-black/50 border-gray-700 hover:bg-black/70 text-white">
<SparklesIcon className="w-4 h-4 mr-2" />
Introducing Chrome Extension for Contextual AutoFilling
</Button>
</div>

<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
Find and apply jobs in seconds.
</h1>

<p className="text-gray-400 text-lg mb-8 max-w-2xl">
Jobflow is your JobGpt. Start for free today.
</p>
</>
) : (
<div className="w-full mb-8">
<JobSearchResults
query={query}
onClose={handleCloseResults}
apiData={jobApiData}
isLoading={isApiLoading}
/>
</div>
</>
) : (
<div className="flex-1 flex flex-col items-center text-center mt-2 px-4">
{!showResults || queryCount >= QUERY_LIMIT ? (
// Show hero content when no results or query limit reached
<>
<div className="flex justify-center mt-4 mb-[10%]">
<Button variant="outline" className="rounded-full bg-black/50 border-gray-700 hover:bg-black/70 text-white">
<SparklesIcon className="w-4 h-4 mr-2" />
Introducing Chrome Extension for Contextual AutoFilling
</Button>
</div>
<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
Find and apply jobs in seconds.
</h1>
<p className="text-gray-400 text-lg mb-8 max-w-2xl">
Jobflow is your JobGpt. Start for free today.
</p>
</>
) : (
// Show job results when results should be displayed
<div className="w-full mb-8">
<JobSearchResults
query={query}
onClose={handleCloseResults}
apiData={jobApiData}
isLoading={isApiLoading}
)}

{/* Chat input always visible */}
<div className='w-[70%]'>
<ChatInput
isLoggedIn={isLoggedIn}
onSubmit={handleChatSubmit}
setShowResumePopup={setShowResumePopup}
/>
</div>
)}

{/* Chat input always visible */}
<div className='w-[60%]'>
<ChatInput
isLoggedIn={isLoggedIn}
onSubmit={handleChatSubmit}
setShowResumePopup={setShowResumePopup} // Pass the first signup handler
/>
</div>

{/* Only show suggestion buttons when no results */}
{(!showResults || queryCount >= QUERY_LIMIT) && (
<div className="flex flex-wrap justify-center gap-3 mt-6">
{['Recharts dashboard', 'Habit tracker', 'Real estate listings', 'Developer portfolio'].map((item) => (
<button
key={item}
className="bg-zinc-700 hover:bg-zinc-600 text-white rounded-lg px-5 py-2 text-sm font-medium"
>
{item}
</button>
))}
</div>
)}

{/* Suggestions buttons shown only when no results */}
{(!showResults || queryCount >= QUERY_LIMIT) && (
<div className="flex gap-4 justify-center mt-4 mb-[10%]">
{['Recharts dashboard', 'Habit tracker', 'Real estate listings', 'Developer portfolio'].map((item) => (
<Button
key={item}
variant="ghost"
className="rounded-full !bg-black/50 !text-gray-500 border border-gray-700 hover:!bg-black/70 hover:!text-white transition-colors duration-200"
>
{item}
</Button>
))}
</div>
)}
</div>
)}

{/* Query Limit Popup */}

{/* Popups */}
{showPopup && <QueryLimitPopup onClose={handleClosePopup} />}

{/* Resume Upload Popup - show only after first signup */}
{showResumePopup && <ResumeUploadPopup onClose={handleCloseResumePopup} />}

{/* Loading overlay */}
{isApiLoading && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
Expand All @@ -205,4 +214,4 @@ const HeroSection = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
);
};

export default HeroSection;
export default HeroSection;
Loading