feat: ✨ add support github emojis, page for commits

by Sivritkin Dmitriy

1@@ -19,13 +19,15 @@
2     "typecheck": "tsc --noEmit --pretty"
3   },
4   "dependencies": {
5+    "@radix-ui/react-avatar": "^1.0.4",
6     "@radix-ui/react-dialog": "^1.0.5",
7     "@radix-ui/react-dropdown-menu": "^2.0.6",
8     "@radix-ui/react-icons": "^1.3.0",
9     "@radix-ui/react-slot": "^1.0.2",
10     "class-variance-authority": "^0.7.0",
11     "clsx": "^2.1.1",
12     "dayjs": "^1.11.11",
13+    "emoji-toolkit": "^8.0.0",
14     "lucide-react": "^0.395.0",
15     "next": "14.2.4",
16     "next-themes": "^0.3.0",@@ -0,0 +1,79 @@
17+"use client";
18+
19+import {
20+  Dialog,
21+  DialogContent,
22+  DialogDescription,
23+  DialogHeader,
24+  DialogTitle,
25+} from "~/shared/ui/dialog";
26+import { useRouter } from "next/navigation";
27+import { useEffect, useState } from "react";
28+import { $api } from "~/shared/api";
29+import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
30+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
31+import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
32+import { convertEmoji } from "~/shared/lib/convert-emoji";
33+
34+export default function CommitModal({
35+  params: { sha },
36+}: {
37+  params: { sha: string };
38+}) {
39+  const [commit, setCommit] = useState<
40+    null | RestEndpointMethodTypes["repos"]["getCommit"]["response"]["data"]
41+  >(null);
42+  const [loading, setLoading] = useState<boolean>(true);
43+
44+  const router = useRouter();
45+
46+  useEffect(() => {
47+    setLoading(true);
48+    const fetchCommit = async () => {
49+      const response = await $api.rest.repos.getCommit({
50+        owner: "velenyx",
51+        repo: "github-commit-explorer",
52+        ref: sha,
53+      });
54+      setCommit(response.data);
55+      setLoading(false);
56+    };
57+
58+    fetchCommit();
59+  }, []);
60+
61+  const aggregatedDiff =
62+    commit?.files?.reduce(
63+      (acc, file) => {
64+        return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
65+      },
66+      String.raw``,
67+    ) || "";
68+
69+  return (
70+    <div>
71+      <Dialog
72+        open={true}
73+        onOpenChange={() => {
74+          router.back();
75+        }}
76+      >
77+        <DialogContent className="max-h-[70vh]">
78+          {loading ? (
79+            <h1>Loading...</h1>
80+          ) : (
81+            <DialogHeader>
82+              <DialogTitle>{convertEmoji(commit.commit.message)}</DialogTitle>
83+              <DialogDescription>
84+                by {commit?.commit.author?.name}
85+              </DialogDescription>
86+              <SyntaxHighlighter showLineNumbers language="diff" style={style}>
87+                {aggregatedDiff}
88+              </SyntaxHighlighter>
89+            </DialogHeader>
90+          )}
91+        </DialogContent>
92+      </Dialog>
93+    </div>
94+  );
95+}@@ -0,0 +1,3 @@
96+export default function DefaultModal() {
97+  return null;
98+}@@ -1,13 +0,0 @@
99-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
100-import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
101-
102-export const DiffComp = ({ text }: { text: string }) => {
103-  return (
104-    <div>
105-      <h1>Diff Component</h1>
106-      <SyntaxHighlighter language="diff" style={style}>
107-        {text}
108-      </SyntaxHighlighter>
109-    </div>
110-  );
111-};@@ -5,7 +5,6 @@ import { cn } from "~/shared/lib/utils";
112 import { Header } from "~/components/header";
113 
114 import "./globals.css";
115-import "react-diff-view/style/index.css";
116 
117 const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" });
118 
119@@ -17,8 +16,10 @@ export const metadata: Metadata = {
120 
121 export default function RootLayout({
122   children,
123+  modal,
124 }: Readonly<{
125   children: React.ReactNode;
126+  modal: React.ReactNode;
127 }>) {
128   return (
129     <html lang="en">
130@@ -36,7 +37,8 @@ export default function RootLayout({
131         >
132           <div className="relative flex min-h-screen flex-col">
133             <Header />
134-            <main className="mb-10 flex-1">{children}</main>
135+            <main className="mb-10 mt-4 flex-1">{children}</main>
136+            {modal}
137           </div>
138         </ThemeProvider>
139       </body>@@ -2,7 +2,9 @@ import { $api } from "~/shared/api";
140 import { Card } from "~/shared/ui/card";
141 import relativeTime from "dayjs/plugin/relativeTime";
142 import dayjs from "dayjs";
143-import { DiffComp } from "~/app/diffcomp";
144+import Link from "next/link";
145+import { Avatar, AvatarImage } from "~/shared/ui/avatar";
146+import { convertEmoji } from "~/shared/lib/convert-emoji";
147 
148 dayjs.extend(relativeTime);
149 
150@@ -19,34 +21,41 @@ export default async function Home() {
151   });
152 
153   const aggregatedDiff = selCommit.data.files?.reduce(
154-    (acc, file) => {
155-      return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
156-    },
157+    (acc, file) => acc + (file.patch || ""),
158     String.raw``,
159   );
160 
161   return (
162-    <div className="container mt-3">
163+    <div className="container">
164       <h1 className="mt-8 text-center text-6xl font-semibold">
165         <span className="text-blue-700">There are</span> {data.data.length}{" "}
166         commits!
167       </h1>
168       <div className="mt-10">
169         {data.data.map((commit) => (
170-          <Card
171-            key={commit.sha}
172-            className="mt-4 cursor-pointer p-5 transition-all hover:border-blue-400"
173-          >
174-            <h2 className="text-2xl">{commit.commit.message}</h2>
175-            <div className="mt-3 flex items-center justify-between">
176-              <p className="text-lg">by {commit.commit.author?.name}</p>
177-              <p className="text-lg">
178-                {dayjs(commit.commit.committer?.date).fromNow()}
179-              </p>
180-            </div>
181-          </Card>
182+          <Link href={`/sha/${commit.sha}`}>
183+            <Card
184+              key={commit.sha}
185+              className="mt-4 cursor-pointer p-5 transition-all hover:border-blue-400 hover:text-blue-400"
186+            >
187+              <h2 className="text-2xl">
188+                {convertEmoji(commit.commit.message)}
189+              </h2>
190+              <div className="mt-3 flex items-center justify-between">
191+                <div className="flex items-center gap-2">
192+                  <span className="text-lg">by</span>
193+                  <Avatar>
194+                    <AvatarImage src={commit.author?.avatar_url} />
195+                  </Avatar>
196+                  <span className="text-lg">{commit.commit.author?.name}</span>
197+                </div>
198+                <p className="text-lg">
199+                  {dayjs(commit.commit.committer?.date).fromNow()}
200+                </p>
201+              </div>
202+            </Card>
203+          </Link>
204         ))}
205-        <DiffComp text={aggregatedDiff} />
206       </div>
207     </div>
208   );@@ -0,0 +1,34 @@
209+import { $api } from "~/shared/api";
210+import { convertEmoji } from "~/shared/lib/convert-emoji";
211+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
212+import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
213+
214+export default async function CommitModal({
215+  params: { sha },
216+}: {
217+  params: { sha: string };
218+}) {
219+  const { data: commit } = await $api.rest.repos.getCommit({
220+    owner: "velenyx",
221+    repo: "github-commit-explorer",
222+    ref: sha,
223+  });
224+
225+  const aggregatedDiff =
226+    commit?.files?.reduce(
227+      (acc, file) => {
228+        return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
229+      },
230+      String.raw``,
231+    ) || "";
232+
233+  return (
234+    <div className="container">
235+      <h1 className="text-3xl">{convertEmoji(commit.commit.message)}</h1>
236+      <p>by {commit?.commit.author?.name}</p>
237+      <SyntaxHighlighter showLineNumbers language="diff" style={style}>
238+        {aggregatedDiff}
239+      </SyntaxHighlighter>
240+    </div>
241+  );
242+}@@ -0,0 +1,7 @@
243+// @ts-ignore
244+import joypixels from "emoji-toolkit";
245+
246+export const convertEmoji = (text: string) => {
247+  joypixels.ascii = true;
248+  return joypixels.shortnameToUnicode(text);
249+};@@ -0,0 +1,50 @@
250+"use client"
251+
252+import * as React from "react"
253+import * as AvatarPrimitive from "@radix-ui/react-avatar"
254+
255+import { cn } from "~/shared/lib/utils"
256+
257+const Avatar = React.forwardRef<
258+  React.ElementRef<typeof AvatarPrimitive.Root>,
259+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
260+>(({ className, ...props }, ref) => (
261+  <AvatarPrimitive.Root
262+    ref={ref}
263+    className={cn(
264+      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
265+      className
266+    )}
267+    {...props}
268+  />
269+))
270+Avatar.displayName = AvatarPrimitive.Root.displayName
271+
272+const AvatarImage = React.forwardRef<
273+  React.ElementRef<typeof AvatarPrimitive.Image>,
274+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
275+>(({ className, ...props }, ref) => (
276+  <AvatarPrimitive.Image
277+    ref={ref}
278+    className={cn("aspect-square h-full w-full", className)}
279+    {...props}
280+  />
281+))
282+AvatarImage.displayName = AvatarPrimitive.Image.displayName
283+
284+const AvatarFallback = React.forwardRef<
285+  React.ElementRef<typeof AvatarPrimitive.Fallback>,
286+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
287+>(({ className, ...props }, ref) => (
288+  <AvatarPrimitive.Fallback
289+    ref={ref}
290+    className={cn(
291+      "flex h-full w-full items-center justify-center rounded-full bg-muted",
292+      className
293+    )}
294+    {...props}
295+  />
296+))
297+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
298+
299+export { Avatar, AvatarImage, AvatarFallback }@@ -622,6 +622,29 @@ __metadata:
300   languageName: node
301   linkType: hard
302 
303+"@radix-ui/react-avatar@npm:^1.0.4":
304+  version: 1.0.4
305+  resolution: "@radix-ui/react-avatar@npm:1.0.4"
306+  dependencies:
307+    "@babel/runtime": "npm:^7.13.10"
308+    "@radix-ui/react-context": "npm:1.0.1"
309+    "@radix-ui/react-primitive": "npm:1.0.3"
310+    "@radix-ui/react-use-callback-ref": "npm:1.0.1"
311+    "@radix-ui/react-use-layout-effect": "npm:1.0.1"
312+  peerDependencies:
313+    "@types/react": "*"
314+    "@types/react-dom": "*"
315+    react: ^16.8 || ^17.0 || ^18.0
316+    react-dom: ^16.8 || ^17.0 || ^18.0
317+  peerDependenciesMeta:
318+    "@types/react":
319+      optional: true
320+    "@types/react-dom":
321+      optional: true
322+  checksum: 10c0/608494c53968085bfcf9b987d80c3ec6720bdb65f78591d53e8bba3b360e86366d48a7dee11405dd443f5a3565432184b95bb9d4954bca1922cc9385a942caaf
323+  languageName: node
324+  linkType: hard
325+
326 "@radix-ui/react-collection@npm:1.0.3":
327   version: 1.0.3
328   resolution: "@radix-ui/react-collection@npm:1.0.3"
329@@ -2038,6 +2061,13 @@ __metadata:
330   languageName: node
331   linkType: hard
332 
333+"emoji-toolkit@npm:^8.0.0":
334+  version: 8.0.0
335+  resolution: "emoji-toolkit@npm:8.0.0"
336+  checksum: 10c0/be58f02c75acf41a237df26b9dec5bf0b9d1a43f4d0e0224e630f1e634ebee487580cefdb3cc533a88f9753034f26078a7b90548581dd1df19697d1ec8409d05
337+  languageName: node
338+  linkType: hard
339+
340 "encoding@npm:^0.1.13":
341   version: 0.1.13
342   resolution: "encoding@npm:0.1.13"
343@@ -2724,6 +2754,7 @@ __metadata:
344   version: 0.0.0-use.local
345   resolution: "github-commit-explorer@workspace:."
346   dependencies:
347+    "@radix-ui/react-avatar": "npm:^1.0.4"
348     "@radix-ui/react-dialog": "npm:^1.0.5"
349     "@radix-ui/react-dropdown-menu": "npm:^2.0.6"
350     "@radix-ui/react-icons": "npm:^1.3.0"
351@@ -2735,6 +2766,7 @@ __metadata:
352     class-variance-authority: "npm:^0.7.0"
353     clsx: "npm:^2.1.1"
354     dayjs: "npm:^1.11.11"
355+    emoji-toolkit: "npm:^8.0.0"
356     eslint: "npm:^8"
357     eslint-config-next: "npm:14.2.4"
358     lucide-react: "npm:^0.395.0"