Skip to content

Commit a4ab74d

Browse files
committed
measurements-add-form.tsx. Clean up and improve components such as UserAuthForm and GlobalVariableOverview, incorporating better handling for loading states and UI adjustments. Added pagination and sorting functionality to ArticleSearchAndGrid. Introduced a new AIStepLoader component for handling asynchronous steps with a progress bar. Additionally, link paths and icons have been corrected for consistency across the application.
Took 1 minute
1 parent fdc7ad3 commit a4ab74d

18 files changed

+796
-263
lines changed

components/AIStepLoader.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use client'
2+
3+
import { useState, useEffect } from 'react'
4+
import { Progress } from "@/components/ui/progress"
5+
6+
interface AILoadingProps {
7+
steps: string[];
8+
currentStepIndex?: number;
9+
}
10+
11+
export default function AIStepLoader({ steps, currentStepIndex: initialStepIndex = 0 }: AILoadingProps) {
12+
const [currentStep, setCurrentStep] = useState(initialStepIndex)
13+
const [text, setText] = useState('')
14+
const [progress, setProgress] = useState(0)
15+
16+
useEffect(() => {
17+
let interval: NodeJS.Timeout
18+
19+
const totalDuration = 20000; // Total duration in milliseconds (20 seconds)
20+
const stepDuration = totalDuration / steps.length; // Duration per step
21+
22+
const typeWriter = (fullText: string, index: number) => {
23+
if (index < fullText.length) {
24+
setText(fullText.slice(0, index + 1))
25+
interval = setTimeout(() => typeWriter(fullText, index + 1), 50)
26+
} else {
27+
setTimeout(() => {
28+
setProgress((currentStep + 1) * (100 / steps.length))
29+
}, stepDuration - (fullText.length * 50)) // Adjust delay to fit within stepDuration
30+
}
31+
}
32+
33+
typeWriter(`${steps[currentStep]}...`, 0)
34+
35+
const stepInterval = setInterval(() => {
36+
setCurrentStep(prev => (prev + 1) % steps.length)
37+
}, totalDuration / steps.length)
38+
39+
return () => {
40+
clearTimeout(interval)
41+
clearInterval(stepInterval)
42+
}
43+
}, [currentStep, steps])
44+
45+
return (
46+
<div className="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-r from-purple-400 via-pink-500 to-red-500 p-4">
47+
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
48+
<div className="h-20 flex items-center justify-center">
49+
<p className="text-xl text-gray-700">
50+
{text}
51+
<span className="animate-pulse">|</span>
52+
</p>
53+
</div>
54+
<Progress value={progress} className="w-full mt-4" />
55+
<p className="text-sm text-gray-600 mt-2 text-center">
56+
{Math.round(progress)}% Complete
57+
</p>
58+
</div>
59+
</div>
60+
)
61+
}

components/ArticleGrid.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ export default function ArticleGrid({ articles }: ArticleGridProps) {
2727
</Link>
2828
)}
2929
<CardHeader className="pb-2">
30-
<Link href={`/researcher/articles/categories/${article.category.slug}`}>
30+
<Link href={`/articles/category/${article.category.slug}`}>
3131
<Badge variant="secondary" className="mb-2">{article.category.name}</Badge>
3232
</Link>
3333
<CardTitle className="text-xl">
34-
<Link href={`/researcher/article/${article.slug}`}
34+
<Link href={`/article/${article.slug}`}
3535
className="hover:underline">
3636
{article.title}
3737
</Link>
@@ -43,7 +43,7 @@ export default function ArticleGrid({ articles }: ArticleGridProps) {
4343
<CardFooter className="mt-auto pt-4 border-t">
4444
<div className="flex flex-wrap gap-2">
4545
{article.tags.map((tag) => (
46-
<Link key={tag.name} href={`/researcher/articles/tags/${tag.slug}`}>
46+
<Link key={tag.name} href={`/articles/tag/${tag.slug}`}>
4747
<Badge variant="outline" className="text-xs">{tag.name}</Badge>
4848
</Link>
4949
))}

components/ArticleSearchBox.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

components/ChatPanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { exampleMessages } from "@/components/ChatExampleQuestions"
1212

1313
import { UserMessage } from "./assistant/Message"
1414
import {useRouter} from "next/navigation";
15+
import { siteConfig } from "@/config/site"
1516

1617
function ChatPanel({ agentData }: { agentData?: Agent | null }) {
1718
const [aiState] = useAIState()
@@ -105,7 +106,7 @@ const router = useRouter();
105106
className="
106107
bg-gradient-to-t from-black/50 to-black bg-clip-text text-transparent dark:from-white/50 dark:to-white"
107108
>
108-
Talk to Wishonia
109+
Talk to {siteConfig.name}
109110
</span>
110111
</h1>
111112
<p

components/Navigation.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@ import {
2020
NavigationMenuTrigger,
2121
navigationMenuTriggerStyle,
2222
} from "@/components/ui/navigation-menu"
23+
import { siteConfig } from "@/config/site"
2324

2425
const components: { title: string; href: string; icon: any }[] = [
2526
{
26-
title: "Wishonia.love",
27-
href: "https://wishonia.love",
27+
title: siteConfig.name,
28+
href: siteConfig.url.base || "",
2829
icon: <Globe size={18} />,
2930
},
30-
{
31-
title: "GitHub",
32-
href: "https://github.com/wishonia/wishonia",
33-
icon: <GithubLogo size={18} />,
34-
},
31+
...(siteConfig.links.github
32+
? [
33+
{
34+
title: "GitHub",
35+
href: siteConfig.links.github,
36+
icon: <GithubLogo size={18} />,
37+
},
38+
]
39+
: []),
3540
]
3641

3742
export default function Navigation() {
Lines changed: 172 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,202 @@
1+
'use client'
2+
13
import { useState, useEffect } from 'react'
24
import ArticleGrid from '@/components/ArticleGrid'
3-
import ArticleSearchBox from '@/components/ArticleSearchBox'
5+
import { ArticleStatus } from '@prisma/client'
46
import { ArticleWithRelations } from '@/lib/agents/researcher/researcher'
57
import { searchArticles } from '@/app/researcher/researcherActions'
8+
import {
9+
Select,
10+
SelectContent,
11+
SelectItem,
12+
SelectTrigger,
13+
SelectValue,
14+
} from "@/components/ui/select"
15+
import { Button } from '@/components/ui/button'
16+
import { Search, SortAsc, Loader2, ChevronLeft, ChevronRight } from "lucide-react"
17+
import { Input } from "@/components/ui/input"
18+
import { Badge } from "@/components/ui/badge"
19+
import { cn } from "@/lib/utils"
20+
21+
type SortOption = {
22+
label: string
23+
field: 'createdAt' | 'updatedAt' | 'publishedAt' | 'title'
24+
order: 'asc' | 'desc'
25+
}
626

27+
const sortOptions: SortOption[] = [
28+
{ label: 'Newest First', field: 'createdAt', order: 'desc' },
29+
{ label: 'Oldest First', field: 'publishedAt', order: 'asc' },
30+
{ label: 'Recently Updated', field: 'updatedAt', order: 'desc' },
31+
{ label: 'Title A-Z', field: 'title', order: 'asc' },
32+
{ label: 'Title Z-A', field: 'title', order: 'desc' },
33+
]
734

835
type ArticleSearchAndGridProps = {
936
categorySlug?: string
1037
tagSlug?: string
38+
authorUsername?: string
39+
status?: ArticleStatus
1140
}
1241

13-
export default function ArticleSearchAndGrid({ categorySlug = '', tagSlug = '' }: ArticleSearchAndGridProps) {
42+
export default function ArticleSearchAndGrid({
43+
categorySlug = '',
44+
tagSlug = '',
45+
authorUsername = '',
46+
status = ArticleStatus.PUBLISH
47+
}: ArticleSearchAndGridProps) {
1448
const [articles, setArticles] = useState<ArticleWithRelations[]>([])
1549
const [isLoading, setIsLoading] = useState(true)
1650
const [searchQuery, setSearchQuery] = useState('')
51+
const [selectedSort, setSelectedSort] = useState<SortOption>(sortOptions[0])
52+
const [currentPage, setCurrentPage] = useState(1)
53+
const [totalPages, setTotalPages] = useState(1)
54+
const pageSize = 12
1755

1856
useEffect(() => {
1957
const fetchArticles = async () => {
20-
setIsLoading(true)
21-
try {
22-
const results = await searchArticles(searchQuery, categorySlug, tagSlug)
23-
setArticles(results as ArticleWithRelations[])
24-
} catch (error) {
25-
console.error('Error fetching articles:', error)
26-
} finally {
27-
setIsLoading(false)
28-
}
58+
setIsLoading(true)
59+
try {
60+
const results = await searchArticles(
61+
searchQuery,
62+
categorySlug,
63+
tagSlug,
64+
authorUsername,
65+
status,
66+
selectedSort.field,
67+
selectedSort.order,
68+
currentPage,
69+
pageSize
70+
)
71+
setArticles(results.articles as ArticleWithRelations[])
72+
setTotalPages(results.totalPages)
73+
} catch (error) {
74+
console.error('Error fetching articles:', error)
75+
} finally {
76+
setIsLoading(false)
77+
}
2978
}
3079
fetchArticles()
31-
}, [categorySlug, tagSlug, searchQuery])
80+
}, [categorySlug, tagSlug, authorUsername, searchQuery, status, selectedSort, currentPage])
3281

3382
const handleSearch = (query: string) => {
3483
setSearchQuery(query)
84+
setCurrentPage(1) // Reset to first page on new search
85+
}
86+
87+
const handleSortChange = (value: string) => {
88+
const option = sortOptions.find(opt => `${opt.field}-${opt.order}` === value)
89+
if (option) {
90+
setSelectedSort(option)
91+
setCurrentPage(1) // Reset to first page on sort change
92+
}
3593
}
3694

3795
return (
38-
<>
39-
<ArticleSearchBox
40-
searchQuery={searchQuery}
41-
setSearchQuery={handleSearch}
42-
isLoading={isLoading}
43-
/>
96+
<div className="space-y-6">
97+
<div className="rounded-lg border bg-card shadow-sm">
98+
<div className="flex flex-col sm:flex-row gap-4 items-center p-4">
99+
<div className="relative flex-1">
100+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
101+
<Input
102+
type="search"
103+
placeholder="Search articles..."
104+
value={searchQuery}
105+
onChange={(e) => handleSearch(e.target.value)}
106+
className={cn(
107+
"w-full pl-9 pr-4",
108+
isLoading && "pr-12"
109+
)}
110+
/>
111+
{isLoading && (
112+
<div className="absolute right-3 top-1/2 -translate-y-1/2">
113+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
114+
</div>
115+
)}
116+
</div>
117+
118+
<div className="flex items-center gap-2">
119+
<SortAsc className="h-4 w-4 text-muted-foreground" />
120+
<Select
121+
value={`${selectedSort.field}-${selectedSort.order}`}
122+
onValueChange={handleSortChange}
123+
>
124+
<SelectTrigger className="w-[180px] bg-background">
125+
<SelectValue placeholder="Sort by..." />
126+
</SelectTrigger>
127+
<SelectContent>
128+
{sortOptions.map((option) => (
129+
<SelectItem
130+
key={`${option.field}-${option.order}`}
131+
value={`${option.field}-${option.order}`}
132+
>
133+
{option.label}
134+
</SelectItem>
135+
))}
136+
</SelectContent>
137+
</Select>
138+
</div>
139+
</div>
140+
141+
{(categorySlug || tagSlug || authorUsername) && (
142+
<div className="px-4 py-2 border-t flex gap-2 flex-wrap">
143+
{categorySlug && (
144+
<Badge variant="secondary">
145+
Category: {categorySlug}
146+
</Badge>
147+
)}
148+
{tagSlug && (
149+
<Badge variant="secondary">
150+
Tag: {tagSlug}
151+
</Badge>
152+
)}
153+
{authorUsername && (
154+
<Badge variant="secondary">
155+
Author: {authorUsername}
156+
</Badge>
157+
)}
158+
</div>
159+
)}
160+
</div>
161+
44162
<ArticleGrid articles={articles} />
163+
45164
{articles.length === 0 && !isLoading && (
46-
<p className="text-center text-muted-foreground mt-6">No articles found.</p>
165+
<div className="text-center py-12">
166+
<p className="text-muted-foreground">No articles found.</p>
167+
<p className="text-sm text-muted-foreground mt-1">
168+
Try adjusting your search or filters
169+
</p>
170+
</div>
171+
)}
172+
173+
{totalPages > 1 && (
174+
<div className="flex justify-center gap-2 mt-6">
175+
<Button
176+
variant="outline"
177+
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
178+
disabled={currentPage === 1}
179+
className="gap-2"
180+
>
181+
<ChevronLeft className="h-4 w-4" />
182+
Previous
183+
</Button>
184+
<div className="flex items-center px-4 rounded-md border bg-background">
185+
<span className="text-sm">
186+
Page {currentPage} of {totalPages}
187+
</span>
188+
</div>
189+
<Button
190+
variant="outline"
191+
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
192+
disabled={currentPage === totalPages}
193+
className="gap-2"
194+
>
195+
Next
196+
<ChevronRight className="h-4 w-4" />
197+
</Button>
198+
</div>
47199
)}
48-
</>
200+
</div>
49201
)
50202
}

0 commit comments

Comments
 (0)