Skip to content

Commit 47701e4

Browse files
Add Application approvals (#14)
Co-authored-by: israel.aristide <israel.aristide@plusgrade.com>
1 parent 24ac94d commit 47701e4

10 files changed

Lines changed: 137 additions & 53 deletions

File tree

app/dashboard/notes/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ export default function Page() {
3737
</div>
3838
</header>
3939
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 ">
40-
<div className="aspect-video auto-rows-min gap-4 md:grid-cols-3 rounded-xl">
4140
<UserNotes/>
42-
</div>
4341
</div>
4442
</main>
4543
)

components/ui/data-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function DataTable<TData, TValue>({
5959
[]
6060
)
6161
const [rowSelection, setRowSelection] = React.useState({})
62-
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({username: false})
62+
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({username: false, actions:false})
6363

6464
const table = useReactTable({
6565
data,

components/unity/applications/application-display.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import { Application } from "@/lib/definitions"
44
import UserColumn from "../notes/user-column"
55
import { timeAgo } from "@/lib/utils"
6+
import { ReactNode } from "react"
67

78
export interface ApplicationDisplayProps {
8-
application: Application
9+
application: Application,
10+
bottomValue?: ReactNode
911
}
1012

1113
export default function ApplicationsDisplay({
12-
application
14+
application,
15+
bottomValue=null
1316
}: ApplicationDisplayProps) {
1417

1518
return (
@@ -57,6 +60,7 @@ export default function ApplicationsDisplay({
5760
<p className="font-semibold inline">Availabilities: </p>
5861
<p className="opacity-50 inline">{application.avail}</p>
5962
</div>
63+
{bottomValue !== null && bottomValue}
6064
</div>
6165
</div>
6266
)

components/unity/applications/applications-list.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Application, EMPTY_GUILD_RESPONSE, UserResponse } from "@/lib/definitio
88
import { useEffect, useState } from "react";
99
import { ApplicationsTableColumns, ApplicationsTableData } from "./columns";
1010
import ApplicationsDisplay from "./application-display";
11+
import { Button } from "@/components/ui/button";
1112

1213

1314
export function ApplicationsList({
@@ -86,11 +87,44 @@ export function ApplicationsList({
8687
data={applicationsData}/>
8788
</div>
8889
<div className="container mx-auto p-4 flex-4">
89-
90+
9091
{
9192

9293
applications.filter(x => x.userSnowflake === selectedUsers[0])[0] &&
93-
<ApplicationsDisplay application={applications.filter(x => x.userSnowflake === selectedUsers[0])[0]}/>
94+
<ApplicationsDisplay bottomValue = {
95+
<div className="text-right pt-4">
96+
{
97+
applications.filter(x => x.userSnowflake === selectedUsers[0])[0] &&
98+
<Button onClick={() => {
99+
100+
const data = applicationsData.filter(x => x.userid === selectedUsers[0])[0];
101+
const indx = applicationsData.indexOf(data);
102+
103+
if (data.approvals.filter(x => x.authorSnowflake === consts.me.id).length === 0)
104+
{
105+
106+
actions.sendApproval(guild.id, applications.filter(x => x.userSnowflake === data.userid)[0]._id);
107+
108+
data.approvals.push({
109+
date: new Date().toUTCString(),
110+
userSnowflake: data.userid,
111+
authorSnowflake: consts.me.id,
112+
guildSnowflake: guild.id
113+
})
114+
115+
applicationsData[indx] = data;
116+
117+
setApplicationsData([...applicationsData]);
118+
119+
}
120+
121+
}}>
122+
Approve
123+
</Button>
124+
}
125+
</div>
126+
} application={applications.filter(x => x.userSnowflake === selectedUsers[0])[0]}/>
127+
94128
}
95129

96130
</div>

components/unity/notes/columns.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,6 @@ export const UserNotesTableColumns: ColumnDef<UserNotesTableData>[] = [
9999
</>)
100100
}
101101
},
102-
103-
{
104-
accessorKey: "notes",
105-
header: "Notes"
106-
},
107102

108103
{
109104
accessorKey: "lastNote",
@@ -113,7 +108,7 @@ export const UserNotesTableColumns: ColumnDef<UserNotesTableData>[] = [
113108
variant="ghost"
114109
onClick={() => column.toggleSorting(column.getIsSorted() === "asc", true)}
115110
>
116-
Last updated
111+
Updated
117112
<ArrowUpDown className="ml-2 h-4 w-4" />
118113
</Button>
119114
</div>
@@ -129,6 +124,11 @@ export const UserNotesTableColumns: ColumnDef<UserNotesTableData>[] = [
129124
}
130125
},
131126

127+
{
128+
accessorKey: "notes",
129+
header: "Notes",
130+
},
131+
132132
{
133133
id: "actions",
134134
cell: ({ row }) => {

components/unity/notes/user-note.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export default function UserNote({
3232

3333
<div className="flex flex-col *:my-2">
3434
<div className="flex align-items-center justify-between">
35-
<MemberLabel user={user} placement="right" />
36-
<p className="text-muted-foreground/50"><span className="text-muted">by</span> {author.username} <span className="text-muted">{timeAgo(parseInt(note.date)*1000)}</span></p>
35+
<MemberLabel user={author} placement="right" />
36+
<p className="text-muted-foreground/50"><span className="text-muted">saved on</span> {user.username} <span className="text-muted">{timeAgo(parseInt(note.date)*1000)}</span></p>
3737
</div>
3838
<p className="border-muted border-2 bg-muted/50 p-2 rounded">{note.note}</p>
3939
</div>

components/unity/notes/user-notes.tsx

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
import { useBackend } from "@/hooks/use-backend";
33
import { useEffect, useState } from "react";
44
import { useSelectedGuildState } from "@/hooks/use-selected-guild-state";
5-
import { transformNotesResponse, UserResponse } from "@/lib/definitions";
5+
import { EMPTY_GUILD_RESPONSE, transformNotesResponse, UserResponse } from "@/lib/definitions";
66
import { UserNotesTableColumns, UserNotesTableData } from "./columns";
77
import { DataTable } from "@/components/ui/data-table";
88
import { NotesDisplay } from "./notes-display";
9+
import { useAuthToken } from "@/hooks/use-auth-token";
910

1011
export default function UserNotes() {
1112

1213
const { actions } = useBackend(false);
1314
const notes : UserNotesTableData[] = [];
15+
const {token} = useAuthToken();
1416
const [notedata, setnoteData] = useState(notes);
1517
const { guild } = useSelectedGuildState();
1618

@@ -19,43 +21,47 @@ export default function UserNotes() {
1921

2022
useEffect(() => {
2123

22-
actions.fetchAllGuildNotes(guild.id).then(x => {
24+
if (guild !== EMPTY_GUILD_RESPONSE && token) {
2325

24-
const noteDatas : UserNotesTableData[] = [];
25-
const notes = transformNotesResponse(x);
26-
27-
const getUsers = async () => {
28-
29-
const usersPromises : Promise<UserResponse>[] = [];
26+
actions.fetchAllGuildNotes(guild.id).then(x => {
3027

31-
notes.forEach(data => {
32-
usersPromises.push(actions.fetchUser(data.userSnowflake));
33-
})
28+
const noteDatas : UserNotesTableData[] = [];
29+
const notes = transformNotesResponse(x);
30+
31+
const getUsers = async () => {
3432

35-
return Promise.all(usersPromises);
33+
const usersPromises : Promise<UserResponse>[] = [];
34+
35+
notes.forEach(data => {
36+
usersPromises.push(actions.fetchUser(data.userSnowflake));
37+
})
38+
39+
return Promise.all(usersPromises);
40+
41+
}
42+
43+
getUsers().then(users => {
44+
45+
users.forEach(user => {
46+
47+
const tableData : UserNotesTableData= {
48+
userid: user.id,
49+
username: user.username,
50+
notes: notes.filter(x=>x.userSnowflake == user.id)[0].noteCount(),
51+
lastNote: parseInt(notes.filter(x=>x.userSnowflake == user.id)[0].getLatestNote().date) * 1000
52+
}
53+
54+
noteDatas.push(tableData);
55+
56+
})
57+
58+
setnoteData(noteDatas)
3659

37-
}
38-
39-
getUsers().then(users => {
40-
41-
users.forEach(user => {
42-
43-
const tableData : UserNotesTableData= {
44-
userid: user.id,
45-
username: user.username,
46-
notes: notes.filter(x=>x.userSnowflake == user.id)[0].noteCount(),
47-
lastNote: parseInt(notes.filter(x=>x.userSnowflake == user.id)[0].getLatestNote().date) * 1000
48-
}
49-
50-
noteDatas.push(tableData);
51-
5260
})
53-
54-
setnoteData(noteDatas)
55-
61+
5662
})
57-
58-
})
63+
64+
}
5965

6066
}, [guild.id])
6167

hooks/use-backend.tsx

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
1-
1+
"use client";
22
import { API_BASE_URL, Application, Approval, DEFAULT_APPLICATION, DEFAULT_APPROVAL, DEFAULT_GUILD_RESP_STRIPPED, DEFAULT_GUILD_RESPONSE, DEFAULT_TRANSCRIPT, DEFAULT_TRANSCRIPT_RESPONSE, DEFAULT_USER_RESPONSE, GuildResponse, GuildResponseStripped, Insight, Note, Transcripts, TranscriptsResponse, UserResponse } from "@/lib/definitions";
33
import * as React from "react"
44

5-
import axios from 'axios'
5+
import axios, { AxiosError, AxiosResponse } from 'axios'
66
import {useAuthToken} from '@/hooks/use-auth-token'
7+
import { useRedirects } from "./use-redirects";
78

89
export function useBackend(init:boolean = true) {
910

1011
const [isTest, setIsTest] = React.useState<boolean>(false);
1112
const [me, setMe] = React.useState(DEFAULT_USER_RESPONSE);
1213
const { token } = useAuthToken();
14+
const {redirectLogout} = useRedirects();
1315

1416
const get = React.useCallback(async (req_uri: string) => await axios.get(req_uri, {
1517
headers: {
1618
'Authorization': token
1719
}
1820
}), [token])
1921

22+
23+
const post = React.useCallback(async (req_uri: string, data: unknown) => await axios.post(req_uri, data, {
24+
headers: {
25+
'Authorization': token
26+
}
27+
}), [token])
28+
29+
30+
const sendApproval = React.useCallback(async (guild: string, id: string) : Promise<AxiosResponse> => {
31+
return await post(`${API_BASE_URL}/api/applications/${guild}/${id}/approve`, {
32+
33+
})
34+
}, [post])
35+
2036
const fetchGuild = React.useCallback(async (id: string) : Promise<GuildResponse> => {
2137

2238
if (isTest) {
@@ -99,16 +115,34 @@ export function useBackend(init:boolean = true) {
99115
}
100116

101117
const req_uri = `${API_BASE_URL}/api/discord/me`
102-
const res = await get(req_uri);
103118

104-
if (req_uri !== res.request.responseURL) {
119+
try {
120+
const res = await get(req_uri);
121+
122+
return res.data;
123+
124+
} catch ( e : unknown ) {
105125

106-
return DEFAULT_USER_RESPONSE;
126+
if (isAxiosError(e)) {
127+
if (e.request.responseURL.endsWith("/login")) {
128+
redirectLogout();
129+
}
130+
}
107131
}
108132

109-
return res.data;
133+
return DEFAULT_USER_RESPONSE;
134+
135+
// eslint-disable-next-line react-hooks/exhaustive-deps
110136
}, [isTest, get])
111137

138+
function isAxiosError(candidate: unknown): candidate is AxiosError {
139+
140+
if (candidate && typeof candidate === 'object' && 'isAxiosError' in candidate) {
141+
return true;
142+
}
143+
return false;
144+
}
145+
112146
const fetchNotes = React.useCallback( async(guildid: string, userid: string) : Promise<Note[]> => {
113147

114148
if (isTest) {
@@ -193,6 +227,7 @@ export function useBackend(init:boolean = true) {
193227

194228
},
195229
actions : {
230+
sendApproval,
196231
fetchGuild,
197232
fetchMe,
198233
fetchMyGuilds,

hooks/use-redirects.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ export function useRedirects() {
3535
const redirectLogin = React.useCallback(() => {
3636
push(`login`)
3737
}, [push])
38+
39+
const redirectLogout = React.useCallback(() => {
40+
push('/api/revoke');
41+
}, [push])
42+
3843
return {
44+
redirectLogout,
3945
redirectAuthLogin,
4046
redirectTranscript,
4147
redirect,

lib/definitions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type Appeal = {
88
}
99

1010
export type Application = {
11+
_id: string;
1112
guildSnowflake: string;
1213
userSnowflake: string;
1314
submitDate: string;

0 commit comments

Comments
 (0)