diff --git a/next/app/go/GoLink.tsx b/next/app/go/GoLink.tsx index ff08c6f5..35efe537 100644 --- a/next/app/go/GoLink.tsx +++ b/next/app/go/GoLink.tsx @@ -2,90 +2,83 @@ import { GoLinkIcon } from "@/components/common/Icons"; import { GoLinkStar } from "@/components/common/Icons"; import { GoLinkEdit } from "@/components/common/Icons"; import { GoLinkDelete } from "@/components/common/Icons"; +import { fetchAuthLevel, goLinksApi } from "@/lib/api"; import { useEffectAsync } from "@/lib/utils"; import { useSession } from "next-auth/react"; import { useCallback, useEffect, useState } from "react"; export interface GoLinkProps { - id: number; - goUrl: string; - url: string; - description: string; - pinned: boolean; - fetchData: () => Promise; + id: number; + goUrl: string; + url: string; + description: string; + pinned: boolean; + fetchData: () => Promise; } -const GoLink: React.FC = ({ id, goUrl, url, description, pinned, fetchData }) => { - const [newTitle, setTitle] = useState(goUrl); - const [newUrl, setUrl] = useState(url); - const [newDescription, setDescription] = useState(description); - const [newPinned, setPinned] = useState(pinned); - const [officer, setOfficer] = useState(false); - - const handleCancel = () => { - setTitle(goUrl); - setUrl(url); - setDescription(description); - setPinned(pinned); - setOfficer(false); - }; +const GoLink: React.FC = ({ + id, + goUrl, + url, + description, + pinned, + fetchData, +}) => { + const [newTitle, setTitle] = useState(goUrl); + const [newUrl, setUrl] = useState(url); + const [newDescription, setDescription] = useState(description); + const [newPinned, setPinned] = useState(pinned); + const [officer, setOfficer] = useState(false); - const editModalId = `edit-golink-${id}`; - const deleteModalId = `delete-golink-${id}`; + const handleCancel = () => { + setTitle(goUrl); + setUrl(url); + setDescription(description); + setPinned(pinned); + setOfficer(false); + }; - const handleEdit = async () => { - try { - const response = await fetch(`http://localhost:3000/api/golinks`, { - method: 'PUT', // Assuming you are using PUT method for editing - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: id, - golink: newTitle, - url: newUrl, - description: newDescription, - isPinned: newPinned, - isPublic: !officer - }), - }); - - if (response.ok) { - (document.getElementById(editModalId) as HTMLDialogElement).close(); - fetchData(); - } - } catch (error) {} - } + const editModalId = `edit-golink-${id}`; + const deleteModalId = `delete-golink-${id}`; - const handleDelete = async () => { - try { - const response = await fetch(`http://localhost:3000/api/golinks`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: id - }), - }); - - if (response.ok) { - handleCancel(); - (document.getElementById(deleteModalId) as HTMLDialogElement).close(); - fetchData(); - } - } catch (error) {} - } + const handleEdit = async () => { + try { + const response = await goLinksApi.update({ + id: id, + golink: newTitle, + url: newUrl, + description: newDescription, + isPinned: newPinned, + isPublic: !officer, + }); + if (response.ok) { + (document.getElementById(editModalId) as HTMLDialogElement).close(); + fetchData(); + } + } catch (error) {} + }; - return ( - <> - {console.log(url)} - - -
+
-
- {pinned && } -

- {goUrl} -

-
-

{description}

-
-
- - - - - - -
-
- -
-

Create GoLink

+ " + > +
+ {pinned && } +

{goUrl}

+
+

{description}

+
+
+ + + + + + +
+ + +
+

Create GoLink

- - + + - + -
- -
+
+ +
-
- -
+
+ +
-
- +
+ -
-
- -
-
+
+
+ +
+
- + -
-
- -
-
-
-
-
- -
-
- -
-

- Are you sure you want to delete this GoLink? -

-
- +
+
+ +
+
+
+
+
+ +
+
+ +
+

+ Are you sure you want to delete this GoLink? +

+
+ -
-
- -
-
+
+
+ +
+
- + -
-
- -
-
-
-
-
- - ); -} +
+
+ +
+
+
+ + + + ); +}; -const EditAndDelete: React.FC = ({ id, goUrl, url, description, pinned } ) => { - const { data: session } = useSession(); - const [isOfficer, setIsOfficer] = useState(false); - - useEffectAsync(async() => { - const response = await fetch("http://localhost:3000/api/authLevel"); - const data = await response.json(); - setIsOfficer(data.isOfficer); - }, []); +const EditAndDelete: React.FC = ({ + id, + goUrl, + url, + description, + pinned, +}) => { + const { data: session } = useSession(); + const [isOfficer, setIsOfficer] = useState(false); - if(isOfficer) { - return ( -
-
-
- -
-
- -
-
-
- ) - } -} + useEffectAsync(async () => { + const data = await fetchAuthLevel(); + setIsOfficer(data.isOfficer); + }, []); + + if (isOfficer) { + return ( +
+
+
+ +
+
+ +
+
+
+ ); + } +}; export default GoLink; diff --git a/next/app/go/GoLinksContainer.tsx b/next/app/go/GoLinksContainer.tsx index 6e996f9d..47f46fff 100644 --- a/next/app/go/GoLinksContainer.tsx +++ b/next/app/go/GoLinksContainer.tsx @@ -1,78 +1,78 @@ -'use client' +"use client"; -import React, { useEffect, useState } from 'react'; -import GoLink, { GoLinkProps } from './GoLink'; +import React, { useEffect, useState } from "react"; +import GoLink, { GoLinkProps } from "./GoLink"; import { GoLinksContainerProps } from "@/app/go/page"; -import { filterGoLinks } from '@/lib/filter'; -import {GoLinkButton} from '@/app/go/MakeNewGoLink' +import { filterGoLinks } from "@/lib/filter"; +import { GoLinkButton } from "@/app/go/MakeNewGoLink"; -const GoLinksContainer: React.FC = ({ goLinkData, fetchData }) => { - const pinnedGoLinks = goLinkData - .filter(data => data.pinned === true) +const GoLinksContainer: React.FC = ({ + goLinkData, + fetchData, +}) => { + const pinnedGoLinks = goLinkData + .filter((data) => data.pinned === true) .map((data, index) => ( - + )); - const unpinnedGoLinks = goLinkData - .filter(data => !data.pinned) + const unpinnedGoLinks = goLinkData + .filter((data) => !data.pinned) .map((data, index) => ( - + )); - const [goLinkList, setGoLinkList] = useState([]); + const [goLinkList, setGoLinkList] = useState([]); - const updateGoLinkList = (givenFilter: string) => { - if (givenFilter === "" || givenFilter === null) { - setGoLinkList([...pinnedGoLinks, ...unpinnedGoLinks]); - } else { - const filteredGoLinkData = filterGoLinks(givenFilter, goLinkData); - setGoLinkList(filteredGoLinkData.map((data, index) => ( - - ))); - } - }; + const updateGoLinkList = (givenFilter: string) => { + if (givenFilter === "" || givenFilter === null) { + setGoLinkList([...pinnedGoLinks, ...unpinnedGoLinks]); + } else { + const filteredGoLinkData = filterGoLinks(givenFilter, goLinkData); + setGoLinkList( + filteredGoLinkData.map((data, index) => ( + + )) + ); + } + }; - useEffect(() => { - updateGoLinkList(""); - }, [goLinkData]); + useEffect(() => { + updateGoLinkList(""); + }, [goLinkData]); - const handleFilterChange = (event: React.ChangeEvent) => { - const givenFilter = event.target.value; - updateGoLinkList(givenFilter); - }; + const handleFilterChange = (event: React.ChangeEvent) => { + const givenFilter = event.target.value; + updateGoLinkList(givenFilter); + }; - if (goLinkData.length === 0) { - return ( -
-
-

+
+

- Go Links -

- -

- GoLinks are a type of URL shortcut that allow you to access the SSE's frequently used - external websites or resources. Important and/or relevant golinks are marked with a gold star. -

-
-
- handleFilterChange(event)} /> -
-
+ GoLinks are a type of URL shortcut that allow you to access the + SSE's frequently used external websites or resources. Important + and/or relevant golinks are marked with a gold star. +

+
+
+ handleFilterChange(event)} + /> +
+
- -
-
- Loading... -
-

- ) - } - else{ - return ( -
-
-

+ +

+
Loading...
+
+ ); + } else { + return ( +
+
+

- Go Links -

- -

- GoLinks are a type of URL shortcut that allow you to access the SSE's frequently used - external websites or resources. Important and/or relevant golinks are marked with a gold star. -

-
- -
- handleFilterChange(event)} /> -
-
+ GoLinks are a type of URL shortcut that allow you to access the + SSE's frequently used external websites or resources. Important + and/or relevant golinks are marked with a gold star. +

+
+ +
+ handleFilterChange(event)} + /> +
+
- - {goLinkList} -
-
- ) - } + " + > + + {goLinkList} +
+ + ); + } }; -export default GoLinksContainer; \ No newline at end of file +export default GoLinksContainer; diff --git a/next/app/go/MakeNewGoLink.tsx b/next/app/go/MakeNewGoLink.tsx index c92707c9..4215369f 100644 --- a/next/app/go/MakeNewGoLink.tsx +++ b/next/app/go/MakeNewGoLink.tsx @@ -2,79 +2,67 @@ import { useSession } from "next-auth/react"; import { useCallback, useEffect, useState } from "react"; import { CreateGoLinkProps } from "./page"; import { useEffectAsync } from "@/lib/utils"; - -export const GoLinkButton: React.FC = ({fetchData}) => { - const { data: session } : any= useSession() - const [title, setTitle] = useState(""); - const [url, setUrl] = useState(""); - const [description, setDescription] = useState(""); - const [pinned, setPinned] = useState(false); - const [officer, setOfficer] = useState(false); - - const handleSetTitle = (givenTitle: string) => { - const title = givenTitle.toLowerCase().split(' ').join('-') - setTitle(title) - } - - const handleCancel = () => { - setTitle(""); - setUrl(""); - setDescription(""); - setPinned(false); - setOfficer(false); - }; - - const handleCreate = async () => { - try { - const response = await fetch('http://localhost:3000/api/golinks', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + session?.accessToken - }, - // In body, make sure parameter names MATCH the ones in api/golinks/route.ts for the POST request - // Left is backend parameter names, right is our front end names - // golink = backend parameter name --- title = frontend parameter name - // - // I messed up when I did this with the edit API call, it wasn't saving the title because of that. - body: JSON.stringify({ - golink: title, - url: url, - description: description, - isPinned: pinned, - isPublic: !officer // If it is officer, it is not public - }), - }); - - if (response.ok) { - handleCancel(); - (document.getElementById('create-golink') as HTMLDialogElement).close(); - fetchData(); +import { goLinksApi, fetchAuthLevel } from "@/lib/api"; + +export const GoLinkButton: React.FC = ({ fetchData }) => { + const { data: session }: any = useSession(); + const [title, setTitle] = useState(""); + const [url, setUrl] = useState(""); + const [description, setDescription] = useState(""); + const [pinned, setPinned] = useState(false); + const [officer, setOfficer] = useState(false); + + const handleSetTitle = (givenTitle: string) => { + const title = givenTitle.toLowerCase().split(" ").join("-"); + setTitle(title); + }; + + const handleCancel = () => { + setTitle(""); + setUrl(""); + setDescription(""); + setPinned(false); + setOfficer(false); + }; + + const handleCreate = async () => { + try { + const response = await goLinksApi.create({ + golink: title, + url: url, + description: description, + isPinned: pinned, + isPublic: !officer, // If it is officer, it is not public + }); + + if (response.ok) { + handleCancel(); + (document.getElementById("create-golink") as HTMLDialogElement).close(); + fetchData(); + } + } catch (error) {} + }; + + const [isOfficer, setIsOfficer] = useState(false); + useEffectAsync(async () => { + const data = await fetchAuthLevel(); + console.log(data); + setIsOfficer(data.isOfficer); + }, []); + + if (isOfficer) { + return ( + <> + - - -
-

Create GoLink

- - - - - - -
- -
- -
- -
- -
- - -
-
- -
-
- - - -
-
- -
-
-
-
-
- - - ); - } -} - -export default GoLinkButton; \ No newline at end of file + > + Create Go Link + + + +
+

Create GoLink

+ + + + + + +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+
+ + + +
+
+ +
+
+
+
+
+ + ); + } +}; + +export default GoLinkButton; diff --git a/next/app/go/page.tsx b/next/app/go/page.tsx index 76b799d0..d7e8bc2a 100644 --- a/next/app/go/page.tsx +++ b/next/app/go/page.tsx @@ -1,43 +1,44 @@ -"use client" +"use client"; import GoLinksContainer from "@/app/go/GoLinksContainer"; import { GoLinkProps } from "./GoLink"; import { useCallback, useEffect, useState } from "react"; +import { goLinksApi } from "@/lib/api"; export interface CreateGoLinkProps { - fetchData: () => Promise; + fetchData: () => Promise; } export interface GoLinksContainerProps { - goLinkData: GoLinkProps[]; - fetchData: () => Promise; + goLinkData: GoLinkProps[]; + fetchData: () => Promise; } const GoLinksPage = () => { - const [goLinkData, setGoLinkData] = useState([]); - const fetchData = useCallback(async() => { - const response = await fetch("http://localhost:3000/api/golinks/public"); - const data = await response.json(); - setGoLinkData(data.map((item: { id: number, golink: string; url: string; description: string; isPinned: boolean; }) => ({ - id: item.id, - goUrl: item.golink, - url: item.url, - description: item.description ?? '', - pinned: item.isPinned, - }))); - }, []) - useEffect(() => { - fetchData() - }, [fetchData]); + const [goLinkData, setGoLinkData]: [any[], any] = useState([]); + const fetchData = useCallback(async () => { + const data = await goLinksApi.fetch(); + setGoLinkData( + data.map((item) => ({ + id: item.id, + goUrl: item.golink, + url: item.url, + description: item.description ?? "", + pinned: item.isPinned, + })) + ); + }, []); + useEffect(() => { + fetchData(); + }, [fetchData]); - useEffect(() => {}, [goLinkData]); - - return ( - <> - - - ) -} + useEffect(() => {}, [goLinkData]); + return ( + <> + + + ); +}; -export default GoLinksPage; \ No newline at end of file +export default GoLinksPage; diff --git a/next/lib/api.ts b/next/lib/api.ts new file mode 100644 index 00000000..7bfa6a61 --- /dev/null +++ b/next/lib/api.ts @@ -0,0 +1,121 @@ +import { DateTime } from "next-auth/providers/kakao"; + +/** + * Remove the "id" key from an object type + */ +export type NoId = { + [P in keyof T as Exclude]: T[P]; +}; + +/** + * Make every key except "id" optional + */ +export type NonExhaustive = { + [P in keyof T]?: T[P]; +} & { id: number }; + +export type GoLink = { + id: number; + golink: string; + url: string; + description: string; + isPinned: boolean; + isPublic: boolean; +}; + +export type Skill = { + id: number; + skill: string; +}; + +export type HourBlock = { + id: number; + weekday: string; + startTime: DateTime; +}; + +export type Mentor = { + id: number; + userId: number; + expirationDate: DateTime; + isActive: boolean; +}; + +export type MentorRead = Mentor & {}; + +export type Schedule = { + id: number; + mentorId: number; + hourBlockId: number; +}; + +export type ScheduleRead = Schedule & { + mentor: Mentor; + hourBlock: HourBlock; +}; + +/** + * A collection of functions to wrap API methods working with a given type + * + * create: create a new item + * + * fetch: fetch all items + * + * update: update an existing item + * + * delete: remove an item + */ +export type ApiWrapper> = { + create: (item: W) => Promise; + fetch: () => Promise; + update: (item: NonExhaustive) => Promise; + delete: (id: number) => Promise; +}; + +function apiWrapperFactory(route: string): ApiWrapper { + return { + create: async (item) => + fetch("/api/" + route, { + method: "POST", + body: JSON.stringify(item), + }), + fetch: async () => + fetch("/api/" + route).then((response) => response.json()), + update: async (item) => + fetch("/api/" + route, { + method: "PUT", + body: JSON.stringify(item), + }), + delete: async (id) => + fetch("/api/" + route, { + method: "DELETE", + body: JSON.stringify({ id }), + }), + }; +} + +export const skillsApi: ApiWrapper = apiWrapperFactory("skills"); +export const hourBlockApi: ApiWrapper = + apiWrapperFactory("hourBlocks"); + +export const goLinksApi: ApiWrapper = { + ...apiWrapperFactory("golinks"), + fetch: async () => + fetch("/api/golinks/public").then((response) => response.json()), +}; + +export const scheduleApi: ApiWrapper = + apiWrapperFactory("schedule"); + +export type AuthLevel = { + isUser: boolean; + isMember: boolean; + isMentor: boolean; + isOfficer: boolean; +}; + +export async function fetchAuthLevel(): Promise { + const response = await fetch("/api/authLevel"); + const data = await response.json(); + return data; +}