Skip to content

Commit d3c738c

Browse files
authored
feat: implement jargons editor (dashboard) (#41)
This Pull request implement the Jargons Editor (dashboard); This dashboard currently welcomes a user, presents them with a link to adding new word to dictionary and displays the current user's contributions stats (currently stats for contributions made via the jargons editor) with insights into their "total words contributed", "pending word contribution", "new word type contribution", and "edited word type contribution"; it does this by performing a search of pull request authored by the current user using a mix labels and pr states. ### Changes Made - Implemented and integrated the `doContributionStats` action on `/editor` page; this action runs on server-side before the `/editor` renders; it fetches all the contribution stats of the current user and returns the insights as required; it uses the GitHub Search API mixing a combination of the stated labels and search qualifiers in the search queries to get stated stats... - **Pending Word Contribution Stats** - it uses the following search qualifiers `repo:[our_repo] is:pull-request type:pr author:@me label:":computer: via word-editor is:unmerged is:open"`, which searches for opened pull requests on our repo stated repository that is authored by current user, with the labels `💻 via word-editor` - **New Word Type Contribution Stats** - it uses the following search qualifiers `repo:[our_repo] is:pull-request type:pr author:@me label:":computer: via word-editor label:":book: new word" is:merged is:closed"`, which searches for merged pull requests on our repo stated repository that is authored by current user, with the labels `💻 via word-editor` and `📖 new word` - **Edited Word Type Contribution Stats** - it uses the following search qualifiers `repo:[our_repo] is:pull-request type:pr author:@me label:":computer: via word-editor label:":book: edit word" is:merged is:closed"`, which searches for merged pull requests on our repo stated repository that is authored by current user, with the labels `💻 via word-editor` and `📖 edit word` - Implemented the `buildStatsUrl` to compute url to searching the particular stats on the main repo using the respective qualifiers; this means on click the built url a user is directed to github pull request search on stated repo with search result displayed specific to stats (powered by search filter) - Implemented 3 sections on the `/editor` i.e. the Jargon Editor (Dashboard) - User Profile Section - Display a welcome message with the current user's name and avatar with data retrieved from authentication data - Contribution CTA Section - Displays a static message and a link to `/editor/new` page to add new word to dictionary - Contribution Stats Section - Displays the users contribution stats; with an extra "Word Contributed" stats field with value manually calculated from the summation of "New Word Type Contribution Stats" and "Edited Word Type Contribution Stats" - Made Some Changes that are not so related - Implemented customization to `Navbar` component back navigation link using props with default value - Fixed overflowing height of the `WordEditor` component island - Added `maintainers_can_modify` to pr creation request in `submit-word` script ### Screencast/Screenshot ![image](https://github.com/babblebey/jargons.dev/assets/25631971/64477aa1-0c53-4a85-9273-8bc4f0ed58a0) [screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.04.16-21_30_22.webm](https://github.com/babblebey/jargons.dev/assets/25631971/dda9875f-3c43-436b-a3b2-220af938cc30)
1 parent 37840ce commit d3c738c

6 files changed

Lines changed: 245 additions & 24 deletions

File tree

src/components/islands/word-editor.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function Editor({ eTitle, eContent, eMetadata, className, submitHandler, action,
8080
onChange={(e) => setTitle(e.target.value)}
8181
/>
8282
<textarea
83-
className="w-full grow resize-none appearance-none border-none focus:outline-none scrollbar"
83+
className="w-full h-1 grow resize-none appearance-none border-none focus:outline-none scrollbar"
8484
value={content}
8585
onChange={(e) => setContent(e.target.value)}
8686
/>
@@ -96,7 +96,7 @@ function Preview({ className, ...props }) {
9696
className={`${className} select-none`}
9797
{...props}
9898
>
99-
<div className="grow overflow-auto space-y-6 rounded-lg border p-5 shadow-lg">
99+
<div className="h-1 grow overflow-auto space-y-6 rounded-lg border p-5 shadow-lg scrollbar">
100100
<DummyPreviewNavbar />
101101

102102
<div className="max-w-4xl space-y-8 mx-auto">
@@ -132,7 +132,7 @@ const DummyPreviewNavbar = () => (
132132
</span>
133133

134134
<div>
135-
<div className="relative w-56 text-sm hidden @md:flex items-center justify-between border pl-2.5 p-1 space-x-2 border-gray-400 rounded-lg cursor-text">
135+
<div className="relative w-56 text-sm hidden @md:flex items-center justify-between border pl-2.5 p-1 space-x-2 border-gray-400 rounded-lg">
136136
<div className="flex items-center text-gray-400 space-x-2">
137137
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-5 h-5">
138138
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />

src/components/navbar.astro

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
---
2+
const { returnNav = { label: "Back", location: "../" } } = Astro.props;
3+
---
4+
15
<nav class="flex items-center justify-between px-5 md:px-6 py-4">
2-
<a href="../" class="flex items-center">
6+
<a href={ returnNav.location } class="flex items-center">
37
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
48
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
59
</svg>
610
<span>
7-
Back
11+
{ returnNav.label }
812
</span>
913
</a>
1014
<slot />
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import app from "../octokit/app.js";
2+
import { decrypt } from "../utils/crypto.js";
3+
import { PROJECT_REPO_DETAILS } from "../../../constants.js";
4+
5+
/**
6+
* Get some jargons contribution stats for current user on the Jargons Editor
7+
* @param {import("astro").AstroGlobal} astroGlobal
8+
*/
9+
export default async function doContributionStats(astroGlobal) {
10+
const { cookies } = astroGlobal;
11+
const { repoFullname, repoMainBranchRef } = PROJECT_REPO_DETAILS;
12+
13+
const accessToken = cookies.get("jargons.dev:token", { decode: value => decrypt(value) });
14+
const userOctokit = app.getUserOctokit({ token: accessToken.value });
15+
16+
/**
17+
* @todo Implement narrowed search to project's main branch
18+
*/
19+
const baseQuery = `repo:${repoFullname} is:pull-request type:pr author:@me label:":computer: via word-editor"`;
20+
const baseStatsUrlQuery = `is:pr author:@me label:":computer: via word-editor"`;
21+
22+
/**
23+
* @todo [thoughts]: would be nice to have all these requests in one, and use a filter to separate by labels
24+
* [potential bottleneck]: no wat to know whether a PR is "merged" considering that "closed" doesn't mean merged
25+
*/
26+
const { data: newType } = await userOctokit.request("GET /search/issues", {
27+
q: `${baseQuery} label:":book: new word" is:merged is:closed`
28+
});
29+
const { data: editType } = await userOctokit.request("GET /search/issues", {
30+
q: `${baseQuery} label:":book: edit word" is:merged is:closed`
31+
});
32+
const { data: pendingType } = await userOctokit.request("GET /search/issues", {
33+
q: `${baseQuery} is:unmerged is:open`
34+
});
35+
36+
return {
37+
newWords: {
38+
count: newType.total_count,
39+
url: buildStatsUrl(repoFullname, `${baseStatsUrlQuery} is:merged is:closed label:":book: new word"`)
40+
},
41+
editedWords: {
42+
count: editType.total_count,
43+
url: buildStatsUrl(repoFullname, `${baseStatsUrlQuery} is:merged is:closed label:":book: edit word"`)
44+
},
45+
pendingWords: {
46+
count: pendingType.total_count,
47+
url: buildStatsUrl(repoFullname, `${baseStatsUrlQuery} is:unmerged is:open`)
48+
}
49+
}
50+
}
51+
52+
/**
53+
* Build URL to the Pull Request list on the project's Repo
54+
* @param {string} repoFullname
55+
* @param {string} queryString
56+
* @returns {string}
57+
*/
58+
function buildStatsUrl(repoFullname, queryString) {
59+
return `https://github.com/${repoFullname}/pulls?q=${encodeURIComponent(queryString)}`;
60+
}

src/lib/submit-word.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export async function submitWord(devJargonsOctokit, userOctokit, action, project
3535
head: `${forkedRepoOwner}:${headBranch}`,
3636
base: baseBranch,
3737
title,
38-
body
38+
body,
39+
maintainers_can_modify: true
3940
});
4041

4142
// DevJargons (bot) App adds related labels to PR

src/pages/editor/index.astro

Lines changed: 168 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,181 @@
11
---
2-
import doAuth from "../../lib/actions/do-auth";
2+
import { Image } from "astro:assets";
3+
import doAuth from "../../lib/actions/do-auth.js";
34
import BaseLayout from "../../layouts/base.astro";
4-
import Navbar from "../../components/navbar.astro";
5-
import { $userData } from "../../lib/stores/user.js";
5+
import { PROJECT_REPO_DETAILS } from "../../../constants.js";
6+
import doContributionStats from "../../lib/actions/do-contribution-stats.js";
67
78
const { url: { pathname }, redirect } = Astro;
89
910
const { isAuthed, authedData: userData } = await doAuth(Astro);
1011
if (!isAuthed) return redirect(`/login?return_to=${encodeURIComponent(pathname)}`);
11-
// @ts-expect-error
12-
$userData.set(userData);
12+
13+
const { newWords, editedWords, pendingWords } = await doContributionStats(Astro);
14+
const totalWords = {
15+
count: newWords.count + editedWords.count,
16+
url: `https://github.com/${PROJECT_REPO_DETAILS.repoFullname}/pulls?q=${encodeURIComponent(`is:pr is:closed is:merged author:@me label:":computer: via word-editor"`)}`
17+
};
1318
---
1419

1520
<BaseLayout
16-
pageTitle="Dictionry"
17-
class="flex flex-col w-full min-h-screen overflow-hidden"
21+
pageTitle="Jargons Editor"
22+
class="flex flex-col w-full min-h-screen"
1823
>
19-
<Navbar>
20-
21-
</Navbar>
22-
23-
<main class="flex flex-col grow px-5 md:px-6 pb-4">
24-
<h1>
25-
The Jargons Editor - Your Dashboard
26-
</h1>
27-
28-
<a href="/editor/new">Add New Word</a>
24+
<main class="w-full max-w-screen-lg mx-auto flex flex-col grow p-5 space-y-3">
25+
<!-- Profile Section -->
26+
<section class="mt-10 flex w-full items-center justify-between md:px-6">
27+
<div class="flex flex-col space-y-6 justify-center h-full py-6">
28+
<span class="relative flex shrink-0 overflow-hidden rounded-full w-20 h-20 border">
29+
<!-- src="https://images.unsplash.com/profile-1606728664311-4bcc76a40e98image?bg=fff&crop=faces&dpr=2&h=150&w=150&auto=format&fit=crop&q=60&ixlib=rb-4.0.3" -->
30+
<Image
31+
src={ userData.avatar_url }
32+
alt={ (userData.name || userData.login) ?? "User avatar" }
33+
width={100}
34+
height={100}
35+
loading="eager"
36+
/>
37+
</span>
38+
<div class="space-y-2">
39+
<h3 class="text-2xl tracking-tight font-semibold">Hey, { userData.login }👋</h3>
40+
<p class="text-base text-gray-500">Welcome to the Jargons Editor, we're super excited to see you here.</p>
41+
</div>
42+
</div>
43+
</section>
44+
45+
<!-- Contribution CTA -->
46+
<section class="rounded-lg border shadow-sm">
47+
<div class="space-y-1.5 p-3 md:p-6 flex flex-col lg:flex-row lg:items-center justify-between gap-4">
48+
<div class="space-y-1">
49+
<h3 class="font-semibold tracking-tight text-2xl">Contribute Words</h3>
50+
<p class="text-sm">
51+
You can contribute new words to the jargons.dev dictionary.
52+
</p>
53+
</div>
54+
<a
55+
class="flex items-center w-fit justify-center no-underline px-4 py-2 rounded-md border border-gray-200 bg-white hover:bg-gray-100 text-sm shadow-sm hover:shadow transition-colors"
56+
href="/editor/new"
57+
>
58+
<span>Start Now</span>
59+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-4 h-4">
60+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
61+
</svg>
62+
</a>
63+
</div>
64+
</section>
65+
66+
<!-- Contribution Stats -->
67+
<section class="">
68+
<div class="flex-col space-y-1 p-6 flex">
69+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
70+
<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
71+
</svg>
72+
<div class="">
73+
<h3 class="text-2xl font-semibold tracking-tight">Contribution Stats</h3>
74+
<p class="text-sm">Your Words contributed to jargons.dev so far...</p>
75+
</div>
76+
</div>
77+
78+
<div class="grid md:grid-cols-2 gap-2">
79+
<!-- Total Words -->
80+
<div class="p-6 space-y-2 rounded-lg border shadow-sm">
81+
<div class="w-full flex justify-between">
82+
<div class="text-3xl font-bold tracking-tighter">
83+
{ totalWords.count }
84+
</div>
85+
{!!totalWords.count && (
86+
<a class="text-sm" href={totalWords.url}>
87+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-4 h-4">
88+
<title>See all Words Contributed</title>
89+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
90+
</svg>
91+
</a>
92+
)}
93+
</div>
94+
<div class="flex items-center gap-2 text-sm">
95+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
96+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.5 11.5 11 14l4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
97+
</svg>
98+
<span class="text-gray-500 dark:text-gray-400">
99+
Contributed Words
100+
</span>
101+
</div>
102+
</div>
103+
104+
<!-- Pending Words -->
105+
<div class="p-6 space-y-2 rounded-lg border shadow-sm">
106+
<div class="w-full flex justify-between">
107+
<div class="text-3xl font-bold tracking-tighter">
108+
{ pendingWords.count }
109+
</div>
110+
{!!pendingWords.count && (
111+
<a class="text-sm" href={pendingWords.url}>
112+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-4 h-4">
113+
<title>See all Pending Word Contribution</title>
114+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
115+
</svg>
116+
</a>
117+
)}
118+
</div>
119+
<div class="flex items-center gap-2 text-sm">
120+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
121+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
122+
</svg>
123+
<span class="text-gray-500 dark:text-gray-400">
124+
Pending Words
125+
</span>
126+
</div>
127+
</div>
128+
129+
<!-- New Words -->
130+
<div class="p-6 space-y-2 rounded-lg border shadow-sm">
131+
<div class="w-full flex justify-between">
132+
<div class="text-3xl font-bold tracking-tighter">
133+
{ newWords.count }
134+
</div>
135+
{!!newWords.count && (
136+
<a class="text-sm" href={newWords.url}>
137+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-4 h-4">
138+
<title>See all New Words Contributed</title>
139+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
140+
</svg>
141+
</a>
142+
)}
143+
</div>
144+
<div class="flex items-center gap-2 text-sm">
145+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
146+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 1 1 0-18c1.052 0 2.062.18 3 .512M7 9.577l3.923 3.923 8.5-8.5M17 14v6m-3-3h6"/>
147+
</svg>
148+
<span class="text-gray-500 dark:text-gray-400">
149+
New Words Contributed
150+
</span>
151+
</div>
152+
</div>
153+
154+
<!-- Edited Word -->
155+
<div class="p-6 space-y-2 rounded-lg border shadow-sm">
156+
<div class="w-full flex justify-between">
157+
<div class="text-3xl font-bold tracking-tighter">
158+
{ editedWords.count }
159+
</div>
160+
{!!editedWords.count && (
161+
<a class="text-sm" href={editedWords.url}>
162+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-4 h-4">
163+
<title>See all Edited Word Contributed</title>
164+
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" />
165+
</svg>
166+
</a>
167+
)}
168+
</div>
169+
<div class="flex items-center gap-2 text-sm">
170+
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
171+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
172+
</svg>
173+
<span class="text-gray-500 dark:text-gray-400">
174+
Edited Words Contribution
175+
</span>
176+
</div>
177+
</div>
178+
</div>
179+
</section>
29180
</main>
30181
</BaseLayout>

src/pages/editor/new/index.astro

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ const action = resolveEditorActionFromPathname(pathname);
2222
pageTitle="Dictionry"
2323
class="flex flex-col w-full min-h-screen overflow-hidden"
2424
>
25-
<Navbar>
25+
<Navbar
26+
returnNav={{
27+
label: "Back to Jargons Editor",
28+
location: "/editor"
29+
}}
30+
>
2631
<WordEditorSubmitButton client:load />
2732
</Navbar>
2833

0 commit comments

Comments
 (0)