Skip to content

Commit 3a6c6e2

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 6afd0db + e1004be commit 3a6c6e2

File tree

6 files changed

+121
-20
lines changed

6 files changed

+121
-20
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import { useParams } from 'next/navigation'
4+
import ArticleSearchAndGrid from "@/components/article/ArticleSearchAndGrid";
5+
6+
type Params = {
7+
tagSlug: string
8+
}
9+
10+
export default function TagArticles() {
11+
const params = useParams<Params>()
12+
const tagSlug = params?.tagSlug ?? ''
13+
14+
return (
15+
<div className="container mx-auto p-4">
16+
<h1 className="text-3xl font-bold mb-6">Articles in {tagSlug}</h1>
17+
<ArticleSearchAndGrid tagSlug={tagSlug} />
18+
</div>
19+
)
20+
}

app/researcher/researcherActions.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const openai = new OpenAI({
1515
})
1616
const prisma = new PrismaClient()
1717

18-
export async function searchArticles(query: string, categorySlug?: string) {
18+
export async function searchArticles(query: string, categorySlug?: string, tagSlug?: string) {
1919
try {
2020
const whereClause: any = {
2121
OR: [
@@ -28,6 +28,13 @@ export async function searchArticles(query: string, categorySlug?: string) {
2828
whereClause.category = { slug: categorySlug };
2929
}
3030

31+
if (tagSlug) {
32+
const tag = await prisma.articleTag.findUnique({ where: { slug: tagSlug } });
33+
if (tag) {
34+
whereClause.tags = { some: { id: tag.id } };
35+
}
36+
}
37+
3138
return await prisma.article.findMany({
3239
where: whereClause,
3340
select: {

components/ArticleGrid.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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={`/research/tags/${tag.name}`}>
46+
<Link key={tag.name} href={`/researcher/articles/tags/${tag.slug}`}>
4747
<Badge variant="outline" className="text-xs">{tag.name}</Badge>
4848
</Link>
4949
))}

components/ArticleRenderer.tsx

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from "react"
22
import Image from "next/image"
3-
import { Check, Clock, Copy, Folder, Link2, Tag, Loader2 } from "lucide-react"
3+
import { Check, Clock, Copy, Folder, Tag, Loader2, Share2, Twitter, Facebook, Linkedin } from "lucide-react"
44

55
import {ArticleWithRelations} from "@/lib/agents/researcher/researcher"
66
import { Badge } from "@/components/ui/badge"
@@ -30,7 +30,7 @@ function GenerateImageButton({
3030
disabled: boolean
3131
}) {
3232
return (
33-
<Button onClick={onClick} disabled={disabled}>
33+
<Button onClick={onClick} disabled={disabled} variant="outline" >
3434
{disabled ? (
3535
<>
3636
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@@ -56,6 +56,7 @@ export default function ArticleRenderer({
5656
const [isGeneratingImage, setIsGeneratingImage] = useState(false)
5757
const [imageUrl, setImageUrl] = useState<string | null>(null)
5858
const [error, setError] = useState("")
59+
const [isCopiedUrl, setIsCopiedUrl] = useState(false)
5960

6061
const {
6162
title,
@@ -123,6 +124,47 @@ ${sources?.map((source) => `- [${source.title}](${source.url})`).join("\n")}
123124
})
124125
}
125126

127+
const copyUrlToClipboard = () => {
128+
const url = window.location.href;
129+
navigator.clipboard
130+
.writeText(url)
131+
.then(() => {
132+
setIsCopiedUrl(true)
133+
toast({
134+
title: "Copied URL to clipboard",
135+
description:
136+
"The article URL has been copied to the clipboard.",
137+
})
138+
setTimeout(() => setIsCopiedUrl(false), 3000) // Reset after 3 seconds
139+
})
140+
.catch((err) => {
141+
console.error("Failed to copy URL: ", err)
142+
toast({
143+
title: "Failed to copy URL",
144+
description: "An error occurred while copying the URL.",
145+
variant: "destructive",
146+
})
147+
})
148+
}
149+
150+
const shareToTwitter = () => {
151+
const url = window.location.href;
152+
const twitterUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`;
153+
window.open(twitterUrl, "_blank");
154+
}
155+
156+
const shareToFacebook = () => {
157+
const url = window.location.href;
158+
const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`;
159+
window.open(facebookUrl, "_blank");
160+
}
161+
162+
const shareToLinkedIn = () => {
163+
const url = window.location.href;
164+
const linkedInUrl = `https://www.linkedin.com/shareArticle?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`;
165+
window.open(linkedInUrl, "_blank");
166+
}
167+
126168
// Add this function to handle article deletion
127169
async function handleDeleteArticle() {
128170
if (!currentUserId) {
@@ -160,6 +202,17 @@ ${sources?.map((source) => `- [${source.title}](${source.url})`).join("\n")}
160202
<CardTitle>{title}</CardTitle>
161203
<CardDescription>{description}</CardDescription>
162204
</CardHeader>
205+
{featuredImage && (
206+
<div className="mx-auto my-4 w-[90%]">
207+
<Image
208+
src={featuredImage}
209+
alt={`Featured image for ${title}`}
210+
width={1024}
211+
height={1024}
212+
className="h-auto w-full rounded-lg"
213+
/>
214+
</div>
215+
)}
163216
<Separator className="mx-auto my-4 w-[90%]" />
164217
<CardContent>
165218
<CustomReactMarkdown>{content}</CustomReactMarkdown>
@@ -178,18 +231,6 @@ ${sources?.map((source) => `- [${source.title}](${source.url})`).join("\n")}
178231
<CardTitle>Article Info</CardTitle>
179232
</CardHeader>
180233
<CardContent className="space-y-2">
181-
{featuredImage && (
182-
<div className="mb-4">
183-
<Image
184-
src={featuredImage}
185-
alt={`Featured image for ${title}`}
186-
width={1024}
187-
height={1024}
188-
className="h-auto w-full rounded-lg"
189-
/>
190-
</div>
191-
)}
192-
193234
<div className="flex items-center space-x-2">
194235
<Folder className="h-4 w-4"/>
195236
<span>{category?.name}</span>
@@ -208,6 +249,7 @@ ${sources?.map((source) => `- [${source.title}](${source.url})`).join("\n")}
208249
</div>
209250
<div className="flex items-center space-x-2">
210251
<Button
252+
variant="outline"
211253
onClick={copyToClipboard}
212254
className="mt-2"
213255
disabled={isCopied}
@@ -225,6 +267,37 @@ ${sources?.map((source) => `- [${source.title}](${source.url})`).join("\n")}
225267
)}
226268
</Button>
227269
</div>
270+
<div className="flex items-center space-x-2">
271+
<Button
272+
variant="outline"
273+
onClick={copyUrlToClipboard}
274+
className="mt-2"
275+
disabled={isCopiedUrl}
276+
>
277+
{isCopiedUrl ? (
278+
<>
279+
<Check className="mr-2 h-4 w-4"/>
280+
Copied URL!
281+
</>
282+
) : (
283+
<>
284+
<Share2 className="mr-2 h-4 w-4"/>
285+
Copy Sharing URL
286+
</>
287+
)}
288+
</Button>
289+
</div>
290+
<div className="flex space-x-4 mt-2">
291+
<Button variant="outline" onClick={shareToTwitter}>
292+
<Twitter className="mr-2 h-4 w-4" />
293+
</Button>
294+
<Button variant="outline" onClick={shareToFacebook}>
295+
<Facebook className="mr-2 h-4 w-4" />
296+
</Button>
297+
<Button variant="outline" onClick={shareToLinkedIn}>
298+
<Linkedin className="mr-2 h-4 w-4" />
299+
</Button>
300+
</div>
228301
<div className="flex flex-col space-y-2">
229302
<GenerateImageButton
230303
onClick={handleGenerateImage}

components/article/ArticleSearchAndGrid.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { searchArticles } from '@/app/researcher/researcherActions'
77

88
type ArticleSearchAndGridProps = {
99
categorySlug?: string
10+
tagSlug?: string
1011
}
1112

12-
export default function ArticleSearchAndGrid({ categorySlug = '' }: ArticleSearchAndGridProps) {
13+
export default function ArticleSearchAndGrid({ categorySlug = '', tagSlug = '' }: ArticleSearchAndGridProps) {
1314
const [articles, setArticles] = useState<ArticleWithRelations[]>([])
1415
const [isLoading, setIsLoading] = useState(true)
1516
const [searchQuery, setSearchQuery] = useState('')
@@ -18,7 +19,7 @@ export default function ArticleSearchAndGrid({ categorySlug = '' }: ArticleSearc
1819
const fetchArticles = async () => {
1920
setIsLoading(true)
2021
try {
21-
const results = await searchArticles(searchQuery, categorySlug)
22+
const results = await searchArticles(searchQuery, categorySlug, tagSlug)
2223
setArticles(results as ArticleWithRelations[])
2324
} catch (error) {
2425
console.error('Error fetching articles:', error)
@@ -27,7 +28,7 @@ export default function ArticleSearchAndGrid({ categorySlug = '' }: ArticleSearc
2728
}
2829
}
2930
fetchArticles()
30-
}, [categorySlug, searchQuery])
31+
}, [categorySlug, tagSlug, searchQuery])
3132

3233
const handleSearch = (query: string) => {
3334
setSearchQuery(query)

tests/researcher.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { findOrCreateArticleByPromptedTopic } from '@/lib/agents/researcher/rese
77

88
const prisma = new PrismaClient();
99

10-
describe("Database-seeder tests", () => {
10+
describe("Research Agent tests", () => {
1111
jest.setTimeout(6000000)
1212

1313
it("Generates article by URL", async () => {

0 commit comments

Comments
 (0)