-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from rocky-linux/feature/news
Finish News System Implementation
- Loading branch information
Showing
15 changed files
with
858 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import type { NextPage } from "next"; | ||
|
||
import { getTranslations } from "next-intl/server"; | ||
import Link from "next/link"; | ||
import { format } from "date-fns"; | ||
|
||
import { getSortedPostsData } from "@/lib/news"; | ||
|
||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
|
||
export async function generateMetadata() { | ||
const t = await getTranslations("news"); | ||
|
||
return { | ||
title: `${t("title")} - Rocky Linux`, | ||
description: `${t("description")}`, | ||
}; | ||
} | ||
|
||
const NewsPage: NextPage = async () => { | ||
const posts = await getSortedPostsData(); | ||
const t = await getTranslations("news"); | ||
|
||
return ( | ||
<div className="py-24 sm:py-32"> | ||
<div className="mx-auto max-w-7xl px-6 lg:px-8"> | ||
<div className="text-center"> | ||
<h2 className="text-3xl font-bold font-display tracking-tight sm:text-4xl"> | ||
{t("title")} | ||
</h2> | ||
<p className="mt-2 text-lg leading-8">{t("description")}</p> | ||
</div> | ||
<div className="mx-auto mt-10 grid max-w-2xl grid-cols-1 gap-8 border-t pt-10 sm:mt-16 sm:pt-16 lg:mx-0 lg:max-w-none lg:grid-cols-3"> | ||
{posts.map((post) => ( | ||
<Link | ||
key={post.slug} | ||
href={`/news/${post.slug}`} | ||
> | ||
<Card key={post.slug}> | ||
<CardHeader> | ||
<CardTitle className="font-display font-bold truncate"> | ||
{post.title} | ||
</CardTitle> | ||
<CardDescription> | ||
{format(new Date(post.date), "MMMM d, yyyy")} | ||
</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="group relative"> | ||
<p className="line-clamp-3 text-sm leading-6"> | ||
{post.excerpt} | ||
</p> | ||
</div> | ||
</CardContent> | ||
</Card> | ||
</Link> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default NewsPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
"use client"; | ||
|
||
import * as React from "react"; | ||
import * as z from "zod"; | ||
import { useForm } from "react-hook-form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
|
||
import { | ||
Popover, | ||
PopoverContent, | ||
PopoverTrigger, | ||
} from "@/components/ui/popover"; | ||
import { Input } from "@/components/ui/input"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { Button } from "@/components/ui/button"; | ||
|
||
export interface MastodonDialogProps { | ||
invalidUrlMsg: string; | ||
urlMsg: string; | ||
shareMsg: string; | ||
mastodonSrMsg: string; | ||
url: string; | ||
} | ||
|
||
const MastodonDialog: React.FC<MastodonDialogProps> = ({ | ||
invalidUrlMsg, | ||
urlMsg, | ||
shareMsg, | ||
mastodonSrMsg, | ||
url, | ||
}) => { | ||
const mastodonShareSchema = z.object({ | ||
instanceUrl: z | ||
.string() | ||
.regex(/^(?:[a-zA-Z0-9\-_]+\.)+[a-zA-Z]{2,}$/, invalidUrlMsg), | ||
}); | ||
|
||
const form = useForm<z.infer<typeof mastodonShareSchema>>({ | ||
resolver: zodResolver(mastodonShareSchema), | ||
defaultValues: { | ||
instanceUrl: "", | ||
}, | ||
}); | ||
|
||
function onSubmit(values: z.infer<typeof mastodonShareSchema>) { | ||
const shareUrl = `https://${values.instanceUrl}/share?text=${url}`; | ||
|
||
window.open(shareUrl, "_blank"); | ||
} | ||
return ( | ||
<> | ||
<Popover> | ||
<PopoverTrigger> | ||
<span className="sr-only">{mastodonSrMsg}</span> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="currentColor" | ||
className="h-5" | ||
viewBox="0 0 16 16" | ||
> | ||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z" /> | ||
</svg> | ||
</PopoverTrigger> | ||
<PopoverContent> | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)}> | ||
<FormField | ||
control={form.control} | ||
name="instanceUrl" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>{urlMsg}</FormLabel> | ||
<FormControl> | ||
<Input | ||
placeholder="mastodon.social" | ||
{...field} | ||
/> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<Button | ||
type="submit" | ||
className="mt-4" | ||
> | ||
{shareMsg} | ||
</Button> | ||
</form> | ||
</Form> | ||
</PopoverContent> | ||
</Popover> | ||
</> | ||
); | ||
}; | ||
|
||
export default MastodonDialog; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { useTranslations } from "next-intl"; | ||
import Link from "next/link"; | ||
|
||
import MastodonDialog from "./MastodonDialog"; | ||
|
||
interface ShareButtonsProps { | ||
url: string; | ||
} | ||
|
||
const ShareButtons = ({ url }: ShareButtonsProps) => { | ||
const t = useTranslations("share"); | ||
|
||
const facebookLink = `https://www.facebook.com/sharer/sharer.php?u=${url}`; | ||
const xLink = `https://twitter.com/intent/tweet?text=${url}`; | ||
const linkedInLink = `https://www.linkedin.com/sharing/share-offsite/?url=${url}`; | ||
|
||
return ( | ||
<> | ||
<h3 className="text-sm text-center mb-2">{t("shareName")}</h3> | ||
<div className="flex space-x-6 justify-center mb-12"> | ||
<MastodonDialog | ||
invalidUrlMsg={t("mastodon.url-valid")} | ||
urlMsg={t("mastodon.url")} | ||
shareMsg={t("shareName")} | ||
mastodonSrMsg={t("mastodon.name")} | ||
url={url} | ||
/> | ||
<Link | ||
href={facebookLink} | ||
className="hover:text-primary" | ||
target="_blank" | ||
> | ||
<span className="sr-only">{t("facebook")}</span> | ||
<svg | ||
fill="currentColor" | ||
viewBox="0 0 24 24" | ||
className="h-6 w-6" | ||
aria-hidden="true" | ||
> | ||
<path | ||
fillRule="evenodd" | ||
d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" | ||
clipRule="evenodd" | ||
/> | ||
</svg> | ||
</Link> | ||
<Link | ||
href={linkedInLink} | ||
className="hover:text-primary" | ||
target="_blank" | ||
> | ||
<span className="sr-only">{t("linkedin")}</span> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="currentColor" | ||
className="h-6 w-6" | ||
aria-hidden="true" | ||
> | ||
<path d="M0 0v24h24v-24h-24zm8 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.397-2.586 7-2.777 7 2.476v6.759z" /> | ||
</svg> | ||
</Link> | ||
<Link | ||
href={xLink} | ||
className="hover:text-primary" | ||
target="_blank" | ||
> | ||
<span className="sr-only">{t("x")}</span> | ||
<svg | ||
fill="currentColor" | ||
viewBox="0 0 24 24" | ||
className="h-6 w-6" | ||
aria-hidden="true" | ||
> | ||
<path d="M13.6823 10.6218L20.2391 3H18.6854L12.9921 9.61788L8.44486 3H3.2002L10.0765 13.0074L3.2002 21H4.75404L10.7663 14.0113L15.5685 21H20.8131L13.6819 10.6218H13.6823ZM11.5541 13.0956L10.8574 12.0991L5.31391 4.16971H7.70053L12.1742 10.5689L12.8709 11.5655L18.6861 19.8835H16.2995L11.5541 13.096V13.0956Z" /> | ||
</svg> | ||
</Link> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default ShareButtons; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import * as React from "react"; | ||
|
||
import { cn } from "@/lib/utils"; | ||
|
||
const Card = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn( | ||
"rounded-xl border bg-card text-card-foreground shadow", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
Card.displayName = "Card"; | ||
|
||
const CardHeader = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("flex flex-col space-y-1.5 p-6", className)} | ||
{...props} | ||
/> | ||
)); | ||
CardHeader.displayName = "CardHeader"; | ||
|
||
const CardTitle = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLHeadingElement> | ||
>(({ className, ...props }, ref) => ( | ||
<h3 | ||
ref={ref} | ||
className={cn("font-semibold leading-none tracking-tight", className)} | ||
{...props} | ||
/> | ||
)); | ||
CardTitle.displayName = "CardTitle"; | ||
|
||
const CardDescription = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLParagraphElement> | ||
>(({ className, ...props }, ref) => ( | ||
<p | ||
ref={ref} | ||
className={cn("text-sm text-muted-foreground", className)} | ||
{...props} | ||
/> | ||
)); | ||
CardDescription.displayName = "CardDescription"; | ||
|
||
const CardContent = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("p-6 pt-0", className)} | ||
{...props} | ||
/> | ||
)); | ||
CardContent.displayName = "CardContent"; | ||
|
||
const CardFooter = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("flex items-center p-6 pt-0", className)} | ||
{...props} | ||
/> | ||
)); | ||
CardFooter.displayName = "CardFooter"; | ||
|
||
export { | ||
Card, | ||
CardHeader, | ||
CardFooter, | ||
CardTitle, | ||
CardDescription, | ||
CardContent, | ||
}; |
Oops, something went wrong.