Skip to content

Commit 436d7b4

Browse files
committed
fix: resolved build issues and skeleton loading bug
1 parent 96a2817 commit 436d7b4

7 files changed

Lines changed: 607 additions & 100 deletions

File tree

app/page.tsx

Lines changed: 39 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

3-
import React, { useState, useEffect } from "react";
3+
import React, { useState, useEffect, Suspense } from "react";
4+
import dynamic from 'next/dynamic';
45
import Link from "next/link";
56
import Image from "next/image";
67
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -11,13 +12,18 @@ import { Switch } from "@/components/ui/switch";
1112
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
1213
import { Skeleton } from "@/components/ui/skeleton";
1314
import { useToast } from "@/hooks/use-toast";
14-
import {
15-
Dialog,
16-
DialogContent,
17-
DialogDescription,
18-
DialogHeader,
19-
DialogTitle,
20-
} from "@/components/ui/dialog";
15+
16+
// Dynamically import Dialog components with SSR disabled
17+
const JobSearchDialog = dynamic(
18+
() => import('@/components/JobSearchDialog').then((mod) => mod.JobSearchDialog),
19+
{ ssr: false }
20+
);
21+
22+
const SuccessDialog = dynamic(
23+
() => import('@/components/SuccessDialog').then((mod) => mod.SuccessDialog),
24+
{ ssr: false }
25+
);
26+
2127
import {
2228
buildLinkedInJobURL,
2329
validateFilters,
@@ -43,6 +49,10 @@ import {
4349
import { useTheme } from "next-themes";
4450

4551
export default function LandingPage() {
52+
const { toast } = useToast();
53+
const { theme, setTheme } = useTheme();
54+
const [copied, setCopied] = useState(false);
55+
// Use the imported JobFilters type from linkedin-url-builder
4656
const [filters, setFilters] = useState<JobFilters>({
4757
keywords: "",
4858
location: "",
@@ -55,18 +65,13 @@ export default function LandingPage() {
5565

5666
const [generatedUrl, setGeneratedUrl] = useState("");
5767
const [error, setError] = useState("");
58-
const [copied, setCopied] = useState(false);
59-
const [isLoading, setIsLoading] = useState(true);
6068
const [showModal, setShowModal] = useState(false);
61-
62-
const { toast } = useToast();
63-
const { theme, setTheme } = useTheme();
64-
65-
useEffect(() => {
66-
setTimeout(() => setIsLoading(false), 1000);
67-
}, []);
69+
const [isLoading, setIsLoading] = useState(true);
6870

6971
useEffect(() => {
72+
// Set loading to false when component mounts
73+
setIsLoading(false);
74+
7075
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
7176
if (showModal && generatedUrl) {
7277
e.preventDefault();
@@ -75,7 +80,9 @@ export default function LandingPage() {
7580
};
7681

7782
window.addEventListener("beforeunload", handleBeforeUnload);
78-
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
83+
return () => {
84+
window.removeEventListener("beforeunload", handleBeforeUnload);
85+
};
7986
}, [showModal, generatedUrl]);
8087

8188
const handleGenerate = () => {
@@ -238,7 +245,7 @@ export default function LandingPage() {
238245
</Label>
239246
<Input
240247
id="keywords"
241-
placeholder="e.g. Python Developer"
248+
placeholder="e.g. Software Engineer"
242249
value={filters.keywords}
243250
onChange={(e) => updateFilter("keywords", e.target.value)}
244251
className={error ? "border-destructive" : ""}
@@ -250,7 +257,7 @@ export default function LandingPage() {
250257
<Label htmlFor="location">Location</Label>
251258
<Input
252259
id="location"
253-
placeholder="e.g. Karachi, Pakistan"
260+
placeholder="e.g. San Francisco, California"
254261
value={filters.location}
255262
onChange={(e) => updateFilter("location", e.target.value)}
256263
/>
@@ -427,64 +434,18 @@ export default function LandingPage() {
427434
</div>
428435
</footer>
429436

430-
<Dialog open={showModal} onOpenChange={setShowModal}>
431-
<DialogContent className="sm:max-w-lg">
432-
<DialogHeader>
433-
<div className="flex items-center gap-2 mb-2">
434-
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
435-
<Check className="h-5 w-5 text-green-600 dark:text-green-400" />
436-
</div>
437-
<DialogTitle className="text-xl">Link generated successfully!</DialogTitle>
438-
</div>
439-
<DialogDescription className="text-base">
440-
{filters.keywords && filters.location
441-
? `${filters.keywords} in ${filters.location}`
442-
: filters.keywords
443-
? filters.keywords
444-
: "Your job search"}
445-
</DialogDescription>
446-
</DialogHeader>
447-
448-
<div className="space-y-4 mt-2">
449-
<div className="space-y-2">
450-
<Label className="text-sm text-muted-foreground">Your LinkedIn Job Search URL</Label>
451-
<div className="flex gap-2">
452-
<Input value={generatedUrl} readOnly className="font-mono text-xs" />
453-
<Button variant="outline" size="icon" onClick={handleCopy}>
454-
{copied ? (
455-
<Check className="h-4 w-4 text-green-600" />
456-
) : (
457-
<Copy className="h-4 w-4" />
458-
)}
459-
</Button>
460-
</div>
461-
</div>
462-
463-
<div className="rounded-lg border border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-950 p-3">
464-
<p className="text-xs text-green-700 dark:text-green-300">
465-
{getFilterSummary(filters)}
466-
</p>
467-
</div>
468-
469-
<div className="flex gap-2 pt-2">
470-
<Button
471-
variant="default"
472-
className="flex-1 bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700"
473-
onClick={() => {
474-
window.open(generatedUrl, "_blank", "noopener,noreferrer");
475-
setShowModal(false);
476-
}}
477-
>
478-
<ExternalLink className="h-4 w-4 mr-2" />
479-
Open in LinkedIn
480-
</Button>
481-
<Button variant="outline" onClick={() => setShowModal(false)}>
482-
Close
483-
</Button>
484-
</div>
485-
</div>
486-
</DialogContent>
487-
</Dialog>
437+
<Suspense fallback={null}>
438+
<SuccessDialog
439+
open={showModal}
440+
onOpenChange={setShowModal}
441+
generatedUrl={generatedUrl}
442+
filters={{
443+
...filters,
444+
location: filters.location || ''
445+
}}
446+
getFilterSummary={getFilterSummary}
447+
/>
448+
</Suspense>
488449
</div>
489450
);
490451
}

components/JobSearchDialog.tsx

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as DialogPrimitive from "@radix-ui/react-dialog";
5+
import { X } from "lucide-react";
6+
import { cn } from "@/lib/utils";
7+
8+
const Dialog = DialogPrimitive.Root;
9+
10+
const DialogTrigger = React.forwardRef<
11+
React.ElementRef<typeof DialogPrimitive.Trigger>,
12+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>
13+
>(({ className, children, ...props }, ref) => (
14+
<DialogPrimitive.Trigger ref={ref} className={cn("", className)} {...props}>
15+
{children}
16+
</DialogPrimitive.Trigger>
17+
));
18+
DialogTrigger.displayName = DialogPrimitive.Trigger.displayName;
19+
20+
const DialogPortal = ({
21+
children,
22+
...props
23+
}: DialogPrimitive.DialogPortalProps) => (
24+
<DialogPrimitive.Portal {...props}>
25+
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
26+
{children}
27+
</div>
28+
</DialogPrimitive.Portal>
29+
);
30+
DialogPortal.displayName = "DialogPortal";
31+
32+
const DialogOverlay = React.forwardRef<
33+
React.ElementRef<typeof DialogPrimitive.Overlay>,
34+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
35+
>(({ className, ...props }, ref) => (
36+
<DialogPrimitive.Overlay
37+
ref={ref}
38+
className={cn(
39+
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
40+
className
41+
)}
42+
{...props}
43+
/>
44+
));
45+
DialogOverlay.displayName = "DialogOverlay";
46+
47+
const DialogContent = React.forwardRef<
48+
React.ElementRef<typeof DialogPrimitive.Content>,
49+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
50+
>(({ className, children, ...props }, ref) => (
51+
<DialogPortal>
52+
<DialogOverlay />
53+
<DialogPrimitive.Content
54+
ref={ref}
55+
className={cn(
56+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
57+
className
58+
)}
59+
{...props}
60+
>
61+
{children}
62+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
63+
<X className="h-4 w-4" />
64+
<span className="sr-only">Close</span>
65+
</DialogPrimitive.Close>
66+
</DialogPrimitive.Content>
67+
</DialogPortal>
68+
));
69+
DialogContent.displayName = "DialogContent";
70+
71+
const DialogHeader = ({
72+
className,
73+
...props
74+
}: React.HTMLAttributes<HTMLDivElement>) => (
75+
<div
76+
className={cn(
77+
"flex flex-col space-y-1.5 text-center sm:text-left",
78+
className
79+
)}
80+
{...props}
81+
/>
82+
);
83+
DialogHeader.displayName = "DialogHeader";
84+
85+
const DialogTitle = React.forwardRef<
86+
React.ElementRef<typeof DialogPrimitive.Title>,
87+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
88+
>(({ className, ...props }, ref) => (
89+
<DialogPrimitive.Title
90+
ref={ref}
91+
className={cn(
92+
"text-lg font-semibold leading-none tracking-tight",
93+
className
94+
)}
95+
{...props}
96+
/>
97+
));
98+
DialogTitle.displayName = "DialogTitle";
99+
100+
const DialogDescription = React.forwardRef<
101+
React.ElementRef<typeof DialogPrimitive.Description>,
102+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
103+
>(({ className, ...props }, ref) => (
104+
<DialogPrimitive.Description
105+
ref={ref}
106+
className={cn("text-sm text-muted-foreground", className)}
107+
{...props}
108+
/>
109+
));
110+
DialogDescription.displayName = "DialogDescription";
111+
112+
export function JobSearchDialog({
113+
open,
114+
onOpenChange,
115+
children,
116+
}: {
117+
open: boolean;
118+
onOpenChange: (open: boolean) => void;
119+
children: React.ReactNode;
120+
}) {
121+
return (
122+
<Dialog open={open} onOpenChange={onOpenChange}>
123+
<DialogContent>
124+
<DialogHeader>
125+
<DialogTitle>Job Search</DialogTitle>
126+
<DialogDescription>
127+
Customize your job search criteria
128+
</DialogDescription>
129+
</DialogHeader>
130+
{children}
131+
</DialogContent>
132+
</Dialog>
133+
);
134+
}
135+
136+
export {
137+
Dialog,
138+
DialogContent,
139+
DialogDescription,
140+
DialogHeader,
141+
DialogTitle,
142+
DialogTrigger,
143+
};

0 commit comments

Comments
 (0)