Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring feature/layout up-to-date with develop #6

Merged
merged 15 commits into from
Feb 19, 2024
Merged
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
2 changes: 1 addition & 1 deletion app/[locale]/news/[slug]/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function SlugNotFound() {
const t = useTranslations("news.notFound");

return (
<div className="pt-10 pb-24 sm:pt-12 sm:pb-32">
<div className="py-24 sm:py-32">
<div className="mx-auto max-w-3xl text-base leading-7">
<h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl mb-12 text-center font-display">
{t("title")}
Expand Down
6 changes: 4 additions & 2 deletions app/[locale]/news/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Date from "@/components/Date";
import ShareButtons from "@/components/shareButtons/ShareButtons";

import { checkIfSlugIsValid, getPostData } from "@/lib/news";
import { notFound } from "next/navigation";
Expand Down Expand Up @@ -29,7 +30,7 @@ export async function generateMetadata({ params }: Props) {
const postData: PostData = await getPostData(slug);

return {
title: postData.title,
title: `${postData.title} - Rocky Linux`,
};
}

Expand All @@ -52,9 +53,10 @@ export default async function Post({ params }: Props) {
{postData.title}
</h1>
<div
className="prose dark:prose-invert prose-headings:font-display prose-a:text-primary prose-pre:bg-muted prose-pre:py-3 prose-pre:px-4 prose-pre:rounded prose-img:rounded-md max-w-none"
className="prose dark:prose-invert prose-headings:font-display prose-a:text-primary prose-pre:bg-muted prose-pre:py-3 prose-pre:px-4 prose-pre:rounded prose-img:rounded-md max-w-none mb-12"
dangerouslySetInnerHTML={{ __html: postData.contentHtml }}
/>
<ShareButtons url={`https://rockylinux.org/news/${params.slug}`} />
</div>
</div>
);
Expand Down
70 changes: 70 additions & 0 deletions app/[locale]/news/page.tsx
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;
104 changes: 104 additions & 0 deletions components/shareButtons/MastodonDialog.tsx
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;
82 changes: 82 additions & 0 deletions components/shareButtons/ShareButtons.tsx
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;
87 changes: 87 additions & 0 deletions components/ui/card.tsx
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,
};
Loading
Loading