diff --git a/components/ActivityGraph.tsx b/components/ActivityGraph.tsx index c6e8613..99bb3c2 100644 --- a/components/ActivityGraph.tsx +++ b/components/ActivityGraph.tsx @@ -1,7 +1,7 @@ import {addDays, format, subDays} from "date-fns"; import ResizeObserver from "react-resize-observer"; import React, {useState} from "react"; -import ReactFrappeChart from "./frappe-chart"; +import ReactFrappeChart from "./standard/ReactFrappeChart"; function makeGraphArr(arr: {createdAt: string}[], numDays: number) { const firstDayOnGraph = subDays(new Date(), numDays); diff --git a/components/ActivityTabs.tsx b/components/ActivityTabs.tsx deleted file mode 100644 index e1c5eea..0000000 --- a/components/ActivityTabs.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, {ReactNode, useState} from "react"; -import ActivityGrid, {ActivityDay} from "./ActivityGrid"; -import {format} from "date-fns"; -import ActivityGraph from "./ActivityGraph"; -import {FiEdit, FiMessageSquare} from "react-icons/fi"; -import Tabs from "./Tabs"; -import {TabInfo} from "../utils/types"; - -export function makeGridArr(arr: {createdAt: string}[]) { - let gridArr: ActivityDay[] = []; - for (let item of arr) { - const existingIndex = gridArr.findIndex(d => d.date === format(new Date(item.createdAt), "yyyy-MM-dd")); - if (existingIndex > -1) gridArr[existingIndex].count += 1; - else gridArr.push({ - date: format(new Date(item.createdAt), "yyyy-MM-dd"), - count: 1, - }); - } - return gridArr; -} - -type TabTypes = "snippets" | "posts" | "all"; - -export default function ActivityTabs({snippetsArr, postsArr, linkedSnippetsArr}: {snippetsArr: {createdAt: string}[], postsArr: {createdAt: string}[], linkedSnippetsArr: {count: number}[]}) { - const [tab, setTab] = useState("posts"); - - const snippetsCount = snippetsArr ? snippetsArr.length : 0; - const postsCount = postsArr ? postsArr.length : 0; - const numLinkedSnippets = !!linkedSnippetsArr.length ? linkedSnippetsArr[0].count : 0; - const percentLinked = numLinkedSnippets ? Math.round(numLinkedSnippets / snippetsCount * 100) : 0; - - const tabInfo: TabInfo[] = [ - { - name: "posts", - icon: , - text: `${postsCount} posts`, - }, - { - name: "snippets", - icon: , - text: `${snippetsCount} snippets`, - }, - { - name: "all", - icon: null, - text: `${percentLinked}% linked`, - }, - ]; - - return ( - <> - - {{ - snippets: ( - - ), posts: ( - - ), all: ( - - ) - }[tab]} - - ); -} \ No newline at end of file diff --git a/components/FilterBanner.tsx b/components/FilterBanner.tsx deleted file mode 100644 index 2e348aa..0000000 --- a/components/FilterBanner.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, {Dispatch, SetStateAction} from 'react'; -import UpBanner from "./UpBanner"; - -export default function FilterBanner({searchQuery, setSearchQuery, tagsQuery, setTagsQuery}: { - searchQuery: string, - setSearchQuery: Dispatch>, - tagsQuery: string[], - setTagsQuery: Dispatch>, -}) { - return (searchQuery || (tagsQuery && !!tagsQuery.length)) ? ( - -
-

Showing matches {searchQuery && `for "${searchQuery}" `}{tagsQuery && !!tagsQuery.length && "tagged "}{tagsQuery.map(tag => "#" + tag + " ")}

- -
-
- ) : <>; -} \ No newline at end of file diff --git a/components/NavbarSwitcher.tsx b/components/NavbarSwitcher.tsx deleted file mode 100644 index 93b09a5..0000000 --- a/components/NavbarSwitcher.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, {Dispatch, SetStateAction, useState} from 'react'; -import useSWR, {responseInterface} from "swr"; -import {DatedObj, ProjectObjWithCounts} from "../utils/types"; -import {fetcher} from "../utils/utils"; -import Link from "next/link"; -import Router, {useRouter} from "next/router"; - -export default function NavbarSwitcher({setOpen}: { setOpen: Dispatch> }) { - const router = useRouter(); - const [searchQuery, setSearchQuery] = useState(""); - const [selectedIndex, setSelectedIndex] = useState(0); - const {data: projects, error: postsError}: responseInterface<{ projects: DatedObj[], count: number }, any> = useSWR(`/api/project?search=${searchQuery}&page=1`, fetcher); - - Router.events.on("routeChangeComplete", () => { - setOpen(false); - }); - - return ( - <> - { - setSelectedIndex(0); - setSearchQuery(e.target.value); - }} - onKeyDown={e => { - const itemsMax = projects ? projects.projects ? projects.projects.length : 0 : 0; - if (e.key === "ArrowDown" && itemsMax > 1) { - setSelectedIndex((selectedIndex + 1 < itemsMax) ? selectedIndex + 1 : 0); - } else if (e.key === "ArrowUp" && itemsMax > 1) { - setSelectedIndex(selectedIndex > 0 ? selectedIndex - 1 : itemsMax - 1); - } else if (e.key === "Enter") { - if (projects && projects.projects && projects.projects.length) { - router.push(`/projects/${projects.projects[selectedIndex]._id}`).then(() => setOpen(false)); - } - } - }} - autoFocus - /> - {projects && projects.projects && projects.projects.length ? ( - <> - {projects.projects.map((project, i) => ( - - - {project.name} - - - ))} - - ) : ( -

No results found

- )} - - ); -} \ No newline at end of file diff --git a/components/PaginationBanner.tsx b/components/PaginationBanner.tsx deleted file mode 100644 index fc5db64..0000000 --- a/components/PaginationBanner.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, {Dispatch, SetStateAction} from 'react'; -import UpBanner from "./UpBanner"; - -export default function PaginationBanner({page, label, setPage, className}: { page: number, label: string, setPage: Dispatch>, className?: string }) { - return page > 1 ? ( - - Showing page {page} of {label} - - - ) : <>; -} \ No newline at end of file diff --git a/components/PaginationBar.tsx b/components/PaginationBar.tsx deleted file mode 100644 index 9f15f28..0000000 --- a/components/PaginationBar.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, {Dispatch, SetStateAction} from "react"; - -export default function PaginationBar({page, count, label, setPage, className}: { - page: number, - count: number, - label: string, - setPage: Dispatch>, - className?: string, -}) { - return ( -
-

- Showing {label} {(page - 1) * 10 + 1} - -{(page < Math.floor(count / 10)) ? page * 10 : count} of {count} -

- {count > 10 && ( -
- {Array.from({length: Math.ceil(count / 10)}, (x, i) => i + 1).map(d => ( - - ))} -
- )} -
- ); -} \ No newline at end of file diff --git a/components/PostFeedItem.tsx b/components/PostFeedItem.tsx deleted file mode 100644 index b1c21a5..0000000 --- a/components/PostFeedItem.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import {DatedObj, PostObjGraph, PostObjWithAuthor} from "../utils/types"; -import Link from "next/link"; -import React, {ReactNode} from "react"; -import {findImages} from "../utils/utils"; -import {format} from "date-fns"; -import readingTime from "reading-time"; -import UpInlineButton from "./style/UpInlineButton"; - -function ProjectButtonsList({post, projectId, top}: { post: DatedObj, projectId: string, top?: boolean, }) { - return ( -
- {top ? "in" : projectId ? "Also in" : "In"} - {post.projectArr.filter(project => project._id !== projectId).map((project, i) => ( - <> - {i !== 0 && ( - and - )} - {project.name} - - ))} -
- ) -} - -export default function PostFeedItem({post, projectId, className, i, notFeed, showAuthor}: { - post: DatedObj, - projectId?: string, - className?: string, - i?: number, - notFeed?: boolean, - showAuthor?: boolean, -}) { - const images = findImages(post.slateBody); - const author = post.authorArr[0]; - - const LinkWrapper = ({children, className}: {children: ReactNode, className?: string}) => ( - - - {children} - - - ) - - return ( -
- {i === 1 && !notFeed && ( -
- )} - {!(i === 0 || (i === 1 && !notFeed)) && ( -
- )} - -

{post.title}

-
- {showAuthor && ( -
- - {`Profile -

{post.authorArr[0].name}

-
- -
- )} - {!!images.length ? ( - - First image in post - - ) : ( - -

{post.body.substr(0, 200)}...

-
- )} -
-
-

- {format(new Date(post.createdAt), "MMMM d, yyyy")} -

- {!showAuthor && !!post.projectArr.filter(project => project._id !== projectId).length && ( - - )} - -
- -

{readingTime(post.body).text} >

-
-
-
- ); -} \ No newline at end of file diff --git a/components/PostItemCard.tsx b/components/PostItemCard.tsx deleted file mode 100644 index 21aa156..0000000 --- a/components/PostItemCard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import {DatedObj, PostObjGraph} from "../utils/types"; -import ellipsize from "ellipsize"; -import readingTime from "reading-time"; -import {format} from "date-fns"; -import React from "react"; -import Link from "next/link"; - -export default function PostItemCard({post}: {post: DatedObj}) { - return ( -
-
-

Post {post.privacy !== "public" && `(${post.privacy})`}

- - -

{ellipsize(post.title, 70)}

-
- -

{readingTime(post.body).text}

-
-
-

- {format(new Date(post.createdAt), "h:mm a")} -

-
-
- ) -} \ No newline at end of file diff --git a/components/ProfileProjectItem.tsx b/components/ProfileProjectItem.tsx deleted file mode 100644 index 9f73b75..0000000 --- a/components/ProfileProjectItem.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import {DatedObj, ProjectObjWithStats, UserObjWithProjects} from "../utils/types"; -import Link from "next/link"; -import H3 from "./style/H3"; -import React, {Dispatch, SetStateAction, useState} from "react"; -import {useSession} from "next-auth/client"; -import {FiActivity, FiX} from "react-icons/fi"; -import UpModal from "./up-modal"; -import SpinnerButton from "./spinner-button"; -import axios from "axios"; -import ellipsize from "ellipsize"; -import UpSEO from "./up-seo"; -import {getWeek} from "date-fns"; - -interface ProfileProjectItemPropsBase { - project: DatedObj, - thisUser: DatedObj, -} - -interface ProfileProjectItemPropsFeatured extends ProfileProjectItemPropsBase { - iter: number, - setIter: Dispatch>, - all?: never, -} - -interface ProfileProjectItemPropsAll extends ProfileProjectItemPropsBase { - iter?: never, - setIter?: never, - all: true, -} - -type ProfileProjectItemProps = ProfileProjectItemPropsFeatured | ProfileProjectItemPropsAll; - -interface WeekStat { - week: number, - count: number, -} - -function getCount(arr: {_id: number, count: number}[], week: number) { - return arr.filter(x => x._id === week).reduce((a, b) => a + b.count, 0) -} - -export default function ProfileProjectItem({project, thisUser, iter, setIter, all}: ProfileProjectItemProps) { - const [session, loading] = useSession(); - const [deleteOpen, setDeleteOpen] = useState(false); - const [deleteLoading, setDeleteLoading] = useState(false); - const isOwner = session && (session.userId === project.userId); - - const thisWeek = getWeek(new Date()); - const weeks = [4, 3, 2, 1, 0].map(d => thisWeek - d); - const weekStats: WeekStat[] = [4, 3, 2, 1, 0].map(d => ({ - week: thisWeek - d, - count: getCount(project.postsArr, thisWeek - d) + getCount(project.snippetsArr, thisWeek - d), - })); - const maxCount = Math.max(...weekStats.map(d => d.count)); - - function onDelete() { - setDeleteLoading(true); - - axios.delete(`/api/project/feature`, { - data: { - id: project._id, - }, - }).then(() => { - setDeleteLoading(false); - setDeleteOpen(false); - setIter(iter + 1); - }).catch(e => { - setDeleteLoading(false); - console.log(e); - }); - } - - return ( -
- - - -

{project.name}

-

{ellipsize(project.description, 50)}

-
- - {weekStats.map(weekStat => ( -
- ))} -
-
- - {isOwner && !all && ( - <> -
- -
- -

Are you sure you want to remove this project from your featured projects?

-
- - Remove - - -
-
- - )} -
- ); -} \ No newline at end of file diff --git a/components/ProfileShell.tsx b/components/ProfileShell.tsx deleted file mode 100644 index 8bd55fb..0000000 --- a/components/ProfileShell.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import {DatedObj, UserObjWithProjects} from "../utils/types"; -import React, {ReactNode, useState} from "react"; -import H3 from "./style/H3"; -import Link from "next/link"; -import Linkify from "react-linkify"; -import ProfileSidebarProjectItem from "./ProfileSidebarProjectItem"; -import {FiMenu, FiX} from "react-icons/fi"; -import UpInlineButton from "./style/UpInlineButton"; - -export default function ProfileShell({thisUser, children, featured, selectedProjectId, isSnippet, isAllProjects}: { - thisUser: DatedObj, - children: ReactNode, - featured?: boolean, - selectedProjectId?: string, - isSnippet?: boolean, - isAllProjects?: boolean, -}) { - const featuredProjects = thisUser.projectsArr.filter(d => thisUser.featuredProjects.includes(d._id)); - const thisProject = selectedProjectId && thisUser.projectsArr.find(d => d._id === selectedProjectId); - - const [drawerOpen, setDrawerOpen] = useState(false); - - const SidebarContents = (props: {mobile?: boolean}) => ( -
- - - {`Profile -

{thisUser.name}

-
- -

{thisUser.bio}

- - {!(featured || isAllProjects) && !featuredProjects.some(d => d._id === selectedProjectId) && ((() => { - const thisProject = thisUser.projectsArr.find(d => d._id === selectedProjectId); - return ( - - ) - })())} - {featuredProjects.map(project => ( - - ))} - -
-
- ) - - return ( - <> -
-
-
-
- - - {`Profile -

{thisUser.name}

-
- {!(featured || isAllProjects) && selectedProjectId && ( - <> - / - - {thisProject.name} - - - )} - {isSnippet && ( - <> - / Snippet - - )} -
-
-
- -
- -
-
- -
-
- {children} -
-
-
- - ); -} \ No newline at end of file diff --git a/components/ProfileSidebarProjectItem.tsx b/components/ProfileSidebarProjectItem.tsx deleted file mode 100644 index e164084..0000000 --- a/components/ProfileSidebarProjectItem.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import Link from "next/link"; -import React from "react"; - -export default function ProfileSidebarProjectItem({name, href, selected, mobile}: { - name: string, - href: string, - selected: boolean, - mobile: boolean, -}) { - - return ( - - - {name} - - - ); -} \ No newline at end of file diff --git a/components/ProjectDashboardDropdown.tsx b/components/ProjectDashboardDropdown.tsx deleted file mode 100644 index 8dd559e..0000000 --- a/components/ProjectDashboardDropdown.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import {FiChevronDown} from "react-icons/fi"; -import MoreMenuItem from "./more-menu-item"; - -export default function ProjectDashboardDropdown({projectId, className}: {projectId: string, className?: string}) { - return ( - - ); -} \ No newline at end of file diff --git a/components/ProjectSnippetBrowser.tsx b/components/ProjectSnippetBrowser.tsx deleted file mode 100644 index 8c79a9d..0000000 --- a/components/ProjectSnippetBrowser.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import {format} from "date-fns"; -import SnippetItemCardReadOnly from "./SnippetItemCardReadOnly"; -import PaginationBar from "./PaginationBar"; -import Skeleton from "react-loading-skeleton"; -import React, {Dispatch, SetStateAction, useState} from "react"; -import Link from "next/link"; -import {snippetsExplainer} from "../utils/copy"; - -export default function ProjectSnippetBrowser({snippets, isOwner, snippetPage, setSnippetPage, projectId}: { - snippets: {snippets: DatedObj[], count: number} | undefined, - isOwner?: boolean, - snippetPage: number, - setSnippetPage: Dispatch>, - projectId?: string, -}) { - const snippetsReady = snippets && snippets.snippets; - - return ( -
-

- {isOwner ? ( - You are viewing this {projectId ? "project" : "user"}'s snippets as a public visitor. To see all your snippets, go to { - projectId ? ( - - project dashboard - - ) : "the dashboards of your projects" - }. - ) : ( - {snippetsExplainer} - )} -

- {snippetsReady ? !!snippets.snippets.length ? ( -
-
- {snippets.snippets.map((item, i, a) => ( - <> - {(i === 0 || format(new Date(item.createdAt), "yyyy-MM-dd") !== format(new Date(a[i - 1].createdAt), "yyyy-MM-dd")) && ( -

{format(new Date(item.createdAt), "EEEE, MMMM d")}

- )} - - - ))} -
- -
- ) : ( -

No public snippets have been published {projectId ? "in this project" : "by this user"} yet.

- ) : ( - - )} -
- ); -} \ No newline at end of file diff --git a/components/ProjectStats.tsx b/components/ProjectStats.tsx deleted file mode 100644 index 8925f84..0000000 --- a/components/ProjectStats.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import ReactFrappeChart from "./frappe-chart"; -import {format} from "date-fns"; -import {arrGraphGenerator, arrToDict} from "../utils/utils"; -import GitHubCalendar from "react-github-contribution-calendar/lib"; -import React from "react"; -import useSWR, {responseInterface} from "swr"; - -export default function ProjectStats({projectId, statsIter = 0}: {projectId: string, statsIter?: number}) { - const {data: stats, error: statsError}: responseInterface<{ postDates: {createdAt: string}[], snippetDates: {createdAt: string}[], linkedSnippetsCount: number }, any> = useSWR(`/api/project/stats?projectId=${projectId}&iter=${statsIter}`); - - const statsReady = stats && stats.postDates && stats.snippetDates; - const numPosts = statsReady ? stats.postDates.length : 0; - const numSnippets = statsReady ? stats.snippetDates.length : 0; - const numLinkedSnippets = statsReady ? stats.linkedSnippetsCount : 0; - const percentLinked = numLinkedSnippets ? Math.round(numLinkedSnippets / numSnippets * 100) : 0; - const snippetDates = statsReady ? arrToDict(stats.snippetDates) : {}; - const postDates = statsReady ? arrToDict(stats.postDates) : {}; - const numGraphDays = 30; - - return ( -
-
-

Overview ({percentLinked}% linked)

-

Linked percentage is the percentage of snippets that are linked to at least one post.

- { - const currDate = new Date(); - const thisDate = +currDate - (1000 * 24 * 3600) * (numGraphDays - 1 - i); - return format(new Date(thisDate), "M/d"); - }), - datasets: [ - { - name: "Snippets", - values: arrGraphGenerator(snippetDates, numGraphDays), - }, - { - name: "Posts", - values: arrGraphGenerator(postDates, numGraphDays), - }, - ], - }} - /> -
-
-
-
-

Snippets ({numSnippets})

- {/* - // @ts-ignore*/} - -
-
-
-

Posts ({numPosts})

- {/* - // @ts-ignore*/} - -
-
-
- ); -} \ No newline at end of file diff --git a/components/SlateBalloon.tsx b/components/SlateBalloon.tsx deleted file mode 100644 index d226d1e..0000000 --- a/components/SlateBalloon.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import {PortalBody} from "@udecode/slate-plugins-ui-fluent"; -import {useEffect, useRef, useState} from "react"; -import { - getBalloonToolbarStyles, - setPositionAtSelection, - Toolbar, ToolbarButton, - useBalloonMove, - useBalloonShow -} from "@udecode/slate-plugins-toolbar"; -import {getSlatePluginType, useTSlate} from "@udecode/slate-plugins-core"; -import { - ELEMENT_CODE_BLOCK, - ELEMENT_H2, ELEMENT_LINK, - MARK_BOLD, - MARK_ITALIC, someNode, - ToolbarElement, - ToolbarLink, - ToolbarMark, unwrapNodes, upsertLinkAtSelection, - useSlatePluginType, wrapLink -} from "@udecode/slate-plugins"; -import {BiBold, BiCheck, BiHeading, BiItalic, BiLink, BiPencil, BiUnlink, BiX} from "react-icons/bi"; -import ellipsize from "ellipsize"; -import {Editor, Node, Transforms} from "slate"; -import normalizeUrl from "normalize-url"; - -export default function SlateBalloon() { - const ref = useRef(null); - const linkEditorRef = useRef(null); - const editor = useTSlate(); - - const [selectionHidden] = useBalloonShow({editor, ref, hiddenDelay: 0}); - useBalloonMove({editor, ref, direction: "top"}); - const [linkHidden, setLinkHidden] = useState(true); - const [onCodeBlock, setOnCodeBlock] = useState(false); - const [linkUrl, setLinkUrl] = useState(""); - const [linkUrlEdit, setLinkUrlEdit] = useState(""); - const [linkEdit, setLinkEdit] = useState(false); - const [linkPosition, setLinkPosition] = useState(null); - - function clearLink() { - setLinkHidden(true); - setLinkUrl(""); - setLinkEdit(false); - setLinkPosition(null); - } - - function checkIfCodeBlock(nodes: any[], indexes: number[]): boolean { - if (!indexes.length) return false; - const thisIndex = indexes[0]; - const thisNode = nodes[thisIndex]; - - if (thisNode.type === "code_block") return true; - if (!thisNode.children || indexes.length === 1) return false; - - const newNodes = thisNode.children; - const newIndexes = indexes.slice(1); - return checkIfCodeBlock(newNodes, newIndexes); - } - - // something like useBalloonShow - useEffect(() => { - if (editor.selection) { - const {anchor, focus} = editor.selection; - const codeType = getSlatePluginType(editor, ELEMENT_CODE_BLOCK); - - const root = Editor.node(editor, editor.selection); - const nodeEntries = Node.nodes(root[0], {from: anchor, to: focus}); - let isCode = checkIfCodeBlock(editor.children, root[1]); - - if (!isCode) { - for (const [node, path] of nodeEntries) { - // @ts-ignore node.type throws error - if (node.type === "code_block") isCode = true; - } - } - - if (isCode) { - setOnCodeBlock(true); - } else { - setOnCodeBlock(false); - } - - // if no selection, just cursor - if (anchor === focus) { - let lastNode = editor; - let types = []; - // traverse document to see if there is a link at the cursor - for (let layerIndex of anchor.path) { - if (lastNode.type) types.push(lastNode.type); - if (lastNode.url && lastNode.url !== linkUrl) { - setLinkUrl(lastNode.url); - setLinkUrlEdit(lastNode.url); - setLinkPosition(editor.selection); - } - lastNode = lastNode.children[layerIndex]; - } - if (types.includes("a")) setLinkHidden(false); - else clearLink(); - } else clearLink(); - } - }, [editor.selection]); - - // replicated useBalloonMove hook - useEffect(() => { - if (editor.selection) { - const {anchor, focus} = editor.selection; - if (ref.current && (anchor === focus)) setPositionAtSelection(ref.current, "top"); - } - }, [editor.selection, ref]); - - function unlink() { - unwrapNodes(editor, {at: linkPosition, match: {type: getSlatePluginType(editor, ELEMENT_LINK)}}); - clearLink(); - } - - function saveLink(newUrl: string) { - const processedUrl = normalizeUrl(newUrl); - const linkType = getSlatePluginType(editor, ELEMENT_LINK); - const [, inlinePath] = Editor.leaf(editor, linkPosition); - // select text that's about to be unlinked (making the path itself unusable) - Transforms.select(editor, inlinePath); - // unlink and re-link selected texts - unwrapNodes(editor, {at: linkPosition, match: {type: linkType}}); - wrapLink(editor, { at: editor.selection, url: processedUrl }); - // collapse cursor back to single point - Transforms.collapse(editor, { edge: 'end' }); - // set balloon state vars - setLinkUrl(processedUrl); - setLinkEdit(false); - } - - useEffect(() => { - const linkEditorEnter = e => { - if (e.key === "Enter") saveLink(e.target.value); - } - - if (linkEditorRef.current) linkEditorRef.current.addEventListener("keyup", linkEditorEnter); - - return () => { - if (linkEditorRef.current) linkEditorRef.current.removeEventListener("keyup", linkEditorEnter); - } - }, [linkEditorRef.current]) - - return ( - - - {!linkHidden ? ( - <> - {linkEdit ? ( - <> - setLinkUrlEdit(e.target.value)} - className="py-1 px-2 w-64" - ref={linkEditorRef} - /> - } onMouseDown={() => saveLink(linkUrlEdit)}/> - - ) : ( - <> - - } onMouseDown={() => setLinkEdit(true)}/> - - )} - } onMouseDown={unlink}/> - - ) : ( - <> - } - /> - } - /> - }/> - }/> - - )} - - - ) -} \ No newline at end of file diff --git a/components/SlateEditor.tsx b/components/SlateEditor.tsx deleted file mode 100644 index 15d54ec..0000000 --- a/components/SlateEditor.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, {Dispatch, ReactNode, SetStateAction, useMemo} from "react"; -import {Node} from "slate"; -import {options, pluginsFactory} from "../utils/slate/slatePlugins"; -import {DndProvider} from "react-dnd"; -import {HTML5Backend} from "react-dnd-html5-backend"; -import {SlatePlugins} from "@udecode/slate-plugins"; -import draggableComponents from "../utils/slate/slateDraggables"; -import SlateBalloon from "./SlateBalloon"; -import SlatePlaceholder from "./SlatePlaceholder"; -import slateWordCount from "../utils/slate/slateWordCount"; - -export default function SlateEditor({body, setBody, projectId, urlName, isPost, id, children}: { - body: Node[], - setBody: Dispatch>, - projectId: string, - urlName: string, - isPost: boolean, - id: string, - children?: ReactNode, -}) { - const pluginsMemo = useMemo(() => pluginsFactory(projectId, urlName, isPost), []); - - return ( - <> - - setBody(newValue)} - plugins={pluginsMemo} - components={draggableComponents} - options={options} - editableProps={{autoFocus: true}} - > - - - {children} - - -

{slateWordCount(body)} words

- - ); -} \ No newline at end of file diff --git a/components/SlateEditorMoveToEnd.tsx b/components/SlateEditorMoveToEnd.tsx deleted file mode 100644 index 382f45c..0000000 --- a/components/SlateEditorMoveToEnd.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import {useEffect} from 'react'; -import {Node, Transforms} from "slate"; -import {useTSlate} from "@udecode/slate-plugins-core"; - -export default function SlateEditorMovetoEnd({initBody, startNode}: { initBody?: Node[], startNode?: number }) { - const editor = useTSlate(); - - useEffect(() => { - try { - if (editor && initBody) { - Transforms.select(editor, [startNode,0]); - Transforms.collapse(editor, {edge: "end"}); - } - } catch (e) {} - }, []); - - return ( - <> - ); -} \ No newline at end of file diff --git a/components/SlatePlaceholder.tsx b/components/SlatePlaceholder.tsx deleted file mode 100644 index a44167d..0000000 --- a/components/SlatePlaceholder.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import {useTSlate} from "@udecode/slate-plugins-core"; -import getIsEmpty from "../utils/slate/getIsEmpty"; - -export default function SlatePlaceholder() { - const {children} = useTSlate(); - const isEmpty = children.length === 1 && children[0].type === "p" && (children[0].text === "" || (children[0].children && children[0].children.every(d => getIsEmpty(d)))); - - return isEmpty ? ( -

Write something great...

- ) : <>; -} \ No newline at end of file diff --git a/components/SlateReadOnly.tsx b/components/SlateReadOnly.tsx deleted file mode 100644 index 0376d92..0000000 --- a/components/SlateReadOnly.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import {Node} from "slate"; -import {withReact} from "slate-react"; -import {createEditorPlugins} from "@udecode/slate-plugins"; -import {pluginsFactory} from "../utils/slate/slatePlugins"; -import {customSerializeHTMLFromNodes} from "../utils/slate/customSerializeHTMLFromNodes"; -import {useEffect, useRef} from "react"; -import SubscriptionInsert from "./SubscriptionInsert"; - -export default function SlateReadOnly({nodes, projectId, projectName, ownerName, isOwner}: { - nodes: Node[], - projectId?: string, - projectName?: string, - ownerName?: string, - isOwner?: boolean -}) { - const editor = withReact(createEditorPlugins()); - const ref = useRef(null); - - useEffect(() => { - // @ts-ignore - if (window.twttr && ref.current) { - // @ts-ignore - window.twttr.widgets.load(ref.current); - } - // @ts-ignore - }, []); - - const htmlString = customSerializeHTMLFromNodes(editor, { - plugins: pluginsFactory(), - nodes: nodes, - }); - - const htmlPieces = htmlString - .split(/(?=(\[\[cta\]\]))|(?<=(\[\[cta\]\]))/g) - .filter((d, i, a) => (i === 0 || a[i - 1] !== undefined) && d !== undefined); - - return ( - <> - {htmlPieces.map((d, i, a) => d === "[[cta]]" ? projectId && projectName && ownerName && (isOwner !== undefined) && ( - <> - {i !== 0 && ( -
- )} - - {i !== a.length - 1 && ( -
- )} - - ) : ( -
- ))} - - ) -} \ No newline at end of file diff --git a/components/SnippetItemCard.tsx b/components/SnippetItemCard.tsx deleted file mode 100644 index aa014ca..0000000 --- a/components/SnippetItemCard.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import SlateReadOnly from "./SlateReadOnly"; -import {format} from "date-fns"; -import React, {Dispatch, SetStateAction, useState} from "react"; -import {FiCheck, FiGlobe, FiLink, FiLock} from "react-icons/fi"; -import UpModal from "./up-modal"; -import SnippetItemInner from "./SnippetItemInner"; -import SnippetItemLinkPreview from "./SnippetItemLinkPreview"; -import {useSession} from "next-auth/client"; -import Link from "next/link"; -import SnippetModalShell from "./SnippetModalShell"; - -export default function SnippetItemCard({snippet, setTagsQuery, iteration, setIteration, setStatsIter, statsIter, availableTags, addNewTags, selectedSnippetIds, setSelectedSnippetIds, showFullDate}: { - snippet: DatedObj, - setTagsQuery: Dispatch>, - iteration: number, - setIteration: Dispatch>, - setStatsIter?: Dispatch>, - statsIter?: number, - availableTags: string[], - addNewTags: (newTags: string[]) => void, - selectedSnippetIds: string[], - setSelectedSnippetIds: Dispatch>, - showFullDate?: boolean, -}) { - const [session, loading] = useSession(); - const [modalOpen, setModalOpen] = useState(false); - - const hasLinkedPosts = snippet.linkedPosts && !!snippet.linkedPosts.length; - const hasTags = snippet.tags && !!snippet.tags.length; - const isSelected = selectedSnippetIds.includes(snippet._id); - - return ( - <> -
- - -
- {session && session.userId !== snippet.userId && ( - {`Profile - )} -

- {format(new Date(snippet.createdAt), showFullDate ? "MMMM d, yyyy 'at' h:mm a" : "h:mm a")} -

-
-
- {snippet.privacy === "private" ? ( - - ) : ( - - - - - - )} -
- {hasLinkedPosts && ( - <> - - {snippet.linkedPosts.length} - - )} -
- {hasTags && ( -
- {snippet.tags.map(tag => ( - - ))} -
- )} -
-
- - - - - - - ); -} \ No newline at end of file diff --git a/components/SnippetItemCardReadOnly.tsx b/components/SnippetItemCardReadOnly.tsx deleted file mode 100644 index 3f312fd..0000000 --- a/components/SnippetItemCardReadOnly.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import SlateReadOnly from "./SlateReadOnly"; -import {format} from "date-fns"; -import React, {useState} from "react"; -import {FiGlobe, FiLink, FiLock} from "react-icons/fi"; -import UpModal from "./up-modal"; -import SnippetItemLinkPreview from "./SnippetItemLinkPreview"; -import {useSession} from "next-auth/client"; -import UpInlineButton from "./style/UpInlineButton"; -import Link from "next/link"; -import {useRouter} from "next/router"; -import SnippetModalReadArea from "./SnippetModalReadArea"; -import SnippetModalShell from "./SnippetModalShell"; - -export default function SnippetItemCardReadOnly({snippet, showFullDate, showProject}: { - snippet: DatedObj, - showFullDate?: boolean, - showProject?: boolean, -}) { - const router = useRouter(); - const [session, loading] = useSession(); - const [modalOpen, setModalOpen] = useState(false); - - const hasLinkedPosts = snippet.linkedPosts && !!snippet.linkedPosts.length; - const hasTags = snippet.tags && !!snippet.tags.length; - const isPublic = snippet.privacy === "public" || (session && session.userId === snippet.userId); - - return ( -
- {isPublic ? ( - <> - {showProject && ( -
- In: - - {snippet.projectArr[0].name} - -
- )} - -
- {session && (session.userId !== snippet.userId) && ( - {`Profile - )} -

- {format(new Date(snippet.createdAt), showFullDate ? "MMMM d, yyyy 'at' h:mm a" : "h:mm a")} -

-
-
- {snippet.privacy === "private" ? ( - - ) : ( - - - - - - )} -
- {hasLinkedPosts && ( - <> - - {snippet.linkedPosts.length} - - )} -
-
- { - setModalOpen(d); - }} wide={true}> - - - - - - ) : ( - <> -
- -

Private snippet

-
-

- {format(new Date(snippet.createdAt), showFullDate ? "MMMM d, yyyy 'at' h:mm a" : "h:mm a")} -

- - )} -
- ); -} \ No newline at end of file diff --git a/components/SnippetItemInner.tsx b/components/SnippetItemInner.tsx deleted file mode 100644 index 0913d1e..0000000 --- a/components/SnippetItemInner.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import React, {Dispatch, SetStateAction, useEffect, useState} from "react"; -import axios from "axios"; -import {Node} from "slate"; -import {useSession} from "next-auth/client"; -import SlateReadOnly from "./SlateReadOnly"; -import SnippetEditor from "./snippet-editor"; -import MoreMenu from "./more-menu"; -import MoreMenuItem from "./more-menu-item"; -import {FiArrowRightCircle, FiEdit2, FiExternalLink, FiGlobe, FiLock, FiTrash} from "react-icons/fi"; -import UpModal from "./up-modal"; -import SpinnerButton from "./spinner-button"; -import ProjectBrowser from "./project-browser"; -import Link from "next/link"; -import SnippetItemLinkPreview from "./SnippetItemLinkPreview"; -import Mousetrap from "mousetrap"; -import SnippetLinkedPosts from "./SnippetLinkedPosts"; -import SnippetModalReadArea from "./SnippetModalReadArea"; - -export default function SnippetItemInner({snippet, iteration, setIteration, setStatsIter, statsIter, availableTags, addNewTags, isList, setTagsQuery, setOpen}: { - snippet: DatedObj, - iteration: number, - setIteration: Dispatch>, - setStatsIter?: Dispatch>, - statsIter?: number, - availableTags: string[], - addNewTags: (newTags: string[]) => void, - setTagsQuery: (tagsQuery: string[]) => void, - isList?: boolean, - setOpen?: Dispatch>, -}) { - const [session, loading] = useSession(); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [isEdit, setIsEdit] = useState(false); - const [isMove, setIsMove] = useState(false); - const [isEditLoading, setIsEditLoading] = useState(false); - const [isPrivacyLoading, setIsPrivacyLoading] = useState(false); - - function onDelete() { - setIsLoading(true); - - axios.delete("/api/snippet", { - data: { - id: snippet._id.toString(), - } - }).then(() => { - setIsLoading(false); - setIsDeleteOpen(false); - if (setStatsIter && (statsIter !== undefined)) setStatsIter(statsIter + 1); - setIteration(iteration + 1); - if (setOpen) setOpen(false); - }).catch(e => { - setIsLoading(false); - console.log(e); - }); - } - - function onCancelEdit(urlName: string) { - setIsEdit(false); - axios.post("/api/cancel-delete-images", {type: "snippet", id: snippet._id.toString()}); - } - - function onSaveEdit(urlName: string, isSnippet: boolean, body: string | Node[], url: string, tags: string[], isSlate?: boolean) { - setIsEditLoading(true); - - axios.post("/api/snippet", { - id: snippet._id, - body: body || "", - url: url || "", - tags: tags || [], - urlName: snippet.urlName, - isSlate: !!isSlate, - }).then(res => { - if (res.data.newTags.length) addNewTags(res.data.newTags); - setIteration(iteration + 1); - setIsEdit(false); - }).catch(e => { - console.log(e); - setIsEditLoading(false); - }); - } - - function onMoveSnippet(selectedProjectId: string, setIsLoading: Dispatch>){ - setIsLoading(true); - - axios.post(`/api/snippet`, {id: snippet._id, projectId: selectedProjectId}).then(() => { - setIsLoading(false); - setIteration(iteration + 1); - setStatsIter(statsIter + 1); - setIsMove(false); - if (setOpen) setOpen(false); - }).catch(e => { - setIsLoading(false); - console.log(e); - }); - } - - function onTogglePrivacy() { - setIsPrivacyLoading(true); - - axios.post("/api/snippet", { - id: snippet._id, - privacy: snippet.privacy === "public" ? "private" : "public", - }).then(() => { - setIteration(iteration + 1); - setIsPrivacyLoading(false); - if (setOpen) setOpen(false); - }).catch(e => { - setIsPrivacyLoading(false); - console.log(e); - }); - } - - useEffect(() => { - function onEditShortcut(e) { - e.preventDefault(); - setIsEdit(true); - } - - function onMoveShortcut(e) { - e.preventDefault(); - setIsMove(true); - } - - Mousetrap.bind("e", onEditShortcut); - - Mousetrap.bind("m", onMoveShortcut); - - return () => { - Mousetrap.unbind("e", onEditShortcut); - Mousetrap.unbind("m", onMoveShortcut); - }; - }, []); - - const ThisMoreMenu = () => ( -
- - } onClick={() => setIsEdit(true)}/> - } onClick={() => setIsMove(true)}/> - : } - onClick={onTogglePrivacy} - disabled={isPrivacyLoading} - /> - {snippet.privacy === "public" && ( - } - href={`/@${snippet.authorArr[0].username}/s/${snippet._id}`} - /> - )} - } onClick={() => setIsDeleteOpen(true)}/> - - -

Are you sure you want to delete this snippet? This cannot be undone.

-
- - Delete - - -
-
- -

Select a project to move this snippet to

- -
-
- ) - - return ( - <> - {(isEdit && session && session.userId === snippet.userId) ? ( - <> -

Editing snippet

- null} - /> - - ) : ( - } - isList={isList} - setTagsQuery={setTagsQuery} - /> - )} - - ) -} \ No newline at end of file diff --git a/components/SnippetItemLinkPreview.tsx b/components/SnippetItemLinkPreview.tsx deleted file mode 100644 index 7ff6450..0000000 --- a/components/SnippetItemLinkPreview.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Link from "next/link"; -import React from "react"; -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import useSWR from "swr"; -import {fetcher} from "../utils/utils"; -import ellipsize from "ellipsize"; - -interface SnippetItemLinkPreviewBaseProps { - small?: boolean, -} - -interface SnippetItemLinkPreviewSnippetProps extends SnippetItemLinkPreviewBaseProps { - snippet: DatedObj, - url?: never, -} - -interface SnippetItemLinkPreviewUrlProps extends SnippetItemLinkPreviewBaseProps { - snippet?: never, - url: string, -} - -type SnippetItemLinkPreviewProps = SnippetItemLinkPreviewSnippetProps | SnippetItemLinkPreviewUrlProps; - -export default function SnippetItemLinkPreview({snippet, url, small}: SnippetItemLinkPreviewProps) { - const thisUrl = url || (snippet && snippet.url); - - const {data: linkPreview, error: linkPreviewError} = useSWR(`/api/link-preview?url=${thisUrl}`, (thisUrl) ? fetcher : () => null); - - return ( - - -
-

{ellipsize(thisUrl, 50)}

- {linkPreview && ( -
-

{linkPreview.title}

- {linkPreview.description && ( -

{ellipsize(linkPreview.description, 140)}

- )} -
- )} -
- {linkPreview && linkPreview.images && !!linkPreview.images.length && ( -
- -
- )} -
- - ) -} \ No newline at end of file diff --git a/components/SnippetLinkedPosts.tsx b/components/SnippetLinkedPosts.tsx deleted file mode 100644 index de9294d..0000000 --- a/components/SnippetLinkedPosts.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import PostFeedItem from "./PostFeedItem"; - -export default function SnippetLinkedPosts({snippet}: {snippet: DatedObj}) { - return !!snippet.linkedPosts.length ? ( -
-
-

Linked posts ({snippet.linkedPosts.length}):

- {snippet.linkedPostsArr.map((d, i) => ( - - ))} -
- ) : <>; -} \ No newline at end of file diff --git a/components/SnippetModalReadArea.tsx b/components/SnippetModalReadArea.tsx deleted file mode 100644 index da3677c..0000000 --- a/components/SnippetModalReadArea.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import React, {Dispatch, ReactNode, SetStateAction} from "react"; -import SnippetItemLinkPreview from "./SnippetItemLinkPreview"; -import SlateReadOnly from "./SlateReadOnly"; -import SnippetLinkedPosts from "./SnippetLinkedPosts"; -import {useSession} from "next-auth/client"; - -export default function SnippetModalReadArea({snippet, thisMoreMenu, isList, setTagsQuery, dark, large}: { - snippet: DatedObj, - thisMoreMenu?: ReactNode, - isList?: boolean, - setTagsQuery?: Dispatch>, - dark?: boolean, - large?: boolean, -}) { - const [session, loading] = useSession(); - - return ( - <> -
-
- {snippet.url && ( - - )} -
- -
- {!!snippet.linkArr.length && ( - <> -
-

Linked resources ({snippet.linkArr.length}):

- {snippet.linkArr.map(d => ( -
- -
- ))} - - )} -
- {session && session.userId === snippet.userId && thisMoreMenu} -
- {isList && ( -
- {snippet.tags && snippet.tags.map(tag => ( - - ))} -
- )} - - - ); -} \ No newline at end of file diff --git a/components/SnippetModalShell.tsx b/components/SnippetModalShell.tsx deleted file mode 100644 index 8c0cff1..0000000 --- a/components/SnippetModalShell.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import {DatedObj, SnippetObjGraph} from "../utils/types"; -import React, {Dispatch, ReactNode, SetStateAction} from "react"; -import {FiExternalLink, FiGlobe, FiLock} from "react-icons/fi"; -import Link from "next/link"; -import {format} from "date-fns"; -import UpInlineButton from "./style/UpInlineButton"; -import {useSession} from "next-auth/client"; - -export default function SnippetModalShell({snippet, setTagsQuery, children}: { - snippet: DatedObj, - setTagsQuery?: Dispatch>, - children: ReactNode, -}) { - const [session, loading] = useSession(); - const hasTags = snippet.tags && !!snippet.tags.length; - - return ( - <> -
-
- {snippet.privacy === "private" ? ( - - ) : ( - - - - - - )} -
-

Posted on {format(new Date(snippet.createdAt), "MMMM d, yyyy 'at' h:mm a")}

- {hasTags && snippet.tags.map((tag, i) => ( - - ))} - {session && (session.userId !== snippet.userId) && ( - <> - by - -
- {`Profile - {snippet.authorArr[0].name} -
-
- - )} - {snippet.privacy === "public" && ( - -
- Open as page - -
-
- )} -
-
- {children} -
- - ); -} \ No newline at end of file diff --git a/components/SubscriptionButton.tsx b/components/SubscriptionButton.tsx deleted file mode 100644 index 88f240e..0000000 --- a/components/SubscriptionButton.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import {useSession} from "next-auth/client"; -import axios from "axios"; -import React, {useState} from "react"; -import UpButton from "./UpButton"; -import SpinnerButton from "./spinner-button"; -import useSWR, {responseInterface} from "swr"; -import {fetcher} from "../utils/utils"; -import UpModal from "./up-modal"; - -export default function SubscriptionButton({projectId}: {projectId: string}) { - const [session, loading] = useSession(); - const [subscribeOpen, setSubscribeOpen] = useState(false); - const [subscribeLoading, setSubscribeLoading] = useState(false); - const [unsubscribeOpen, setUnsubscribeOpen] = useState(false); - const [unsubscribeLoading, setUnsubscribeLoading] = useState(false); - const [subscribeIter, setSubscribeIter] = useState(0); - const [email, setEmail] = useState(""); - const [unauthedSubscribeLoading, setUnauthedSubscribeLoading] = useState(false); - const [subscribeDone, setSubscribeDone] = useState(false); - const [subscribeNew, setSubscribeNew] = useState(false); - - const {data: subscribed, error: subscribedError}: responseInterface<{subscribed: boolean}, any> = useSWR(`/api/subscription?authed=true&projectId=${projectId}&iter=${subscribeIter}`, session ? fetcher : () => null); - - function onAuthedSubscribe() { - setSubscribeLoading(true); - - axios.post(`/api/subscription?authed=true&projectId=${projectId}`).then(() => { - setSubscribeIter(subscribeIter + 1); - setSubscribeLoading(false); - }).catch(e => { - console.log(e); - setSubscribeLoading(false); - }); - } - - function onAuthedUnsubscribe() { - setUnsubscribeLoading(true); - - axios.delete(`/api/subscription?authed=true&projectId=${projectId}`).then(() => { - setSubscribeIter(subscribeIter + 1); - setUnsubscribeLoading(false); - setUnsubscribeOpen(false); - }).catch(e => { - console.log(e); - setUnsubscribeLoading(false); - }); - } - - function onUnauthedSubscribe() { - setUnauthedSubscribeLoading(true); - - axios.post(`/api/subscription?email=${email}&projectId=${projectId}`).then(res => { - if (!res.data.exists) setSubscribeNew(true); - setSubscribeDone(true); - setEmail(""); - setUnauthedSubscribeLoading(false); - }).catch(e => { - console.log(e); - setUnauthedSubscribeLoading(false); - }); - } - - return ( - <> - {session ? (subscribed && subscribed.subscribed) ? ( - setUnsubscribeOpen(true)}> - Unsubscribe - - ) : ( - - Subscribe - - ) : ( - setSubscribeOpen(true)}> - Subscribe - - )} - - {subscribeDone ? subscribeNew ? ( - <> -

Check your email for a link to confirm your subscription.

- { - setSubscribeOpen(false); - setSubscribeDone(false); - setSubscribeNew(false); - setEmail(""); - }}>Okay - - ) : ( - <> -

This email is already subscribed to this project.

- { - setSubscribeOpen(false); - setSubscribeDone(false); - setSubscribeNew(false); - setEmail(""); - }}>Okay - - ) : ( - <> -

Subscribe to this project to get notified via email when a new post is published.

- setEmail(e.target.value)} - /> -
- - Subscribe - - setSubscribeOpen(false)}> - Cancel - -
- - )} -
- -

Are you sure you want to unsubscribe from this project?

-
- - Unsubscribe - - setUnsubscribeOpen(false)}> - Cancel - -
-
- - ); -} \ No newline at end of file diff --git a/components/SubscriptionInsert.tsx b/components/SubscriptionInsert.tsx deleted file mode 100644 index c7fad27..0000000 --- a/components/SubscriptionInsert.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import SubscriptionButton from "./SubscriptionButton"; - -export default function SubscriptionInsert({projectId, projectName, ownerName, isOwner}: { projectId: string, projectName: string, ownerName: string, isOwner: boolean }) { - return ( -
-

Subscribe to this project

- {isOwner ? ( -

Viewers of your post will see a call-to-action to subscribe with their email here.

- ) : ( - <> -

Subscribe to get an email whenever a new post is published in {projectName} by {ownerName}

- - - )} -
- ); -} \ No newline at end of file diff --git a/components/SubscriptionItem.tsx b/components/SubscriptionItem.tsx deleted file mode 100644 index e06849e..0000000 --- a/components/SubscriptionItem.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import {DatedObj, SubscriptionObjGraph} from "../utils/types"; -import {Dispatch, SetStateAction, useState} from "react"; -import axios from "axios"; -import Link from "next/link"; -import UpButton from "./UpButton"; -import UpModal from "./up-modal"; -import SpinnerButton from "./spinner-button"; - -export default function SubscriptionItem({subscription, setIter, iter, emailHash, i}: { - subscription: DatedObj, - setIter: Dispatch>, - iter: number, - emailHash: string, - i: number, -}) { - const [isLoading, setIsLoading] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); - - function onUnsubscribe() { - setIsLoading(true); - axios.delete(`/api/subscription?emailHash=${encodeURIComponent(emailHash)}&projectId=${subscription.targetId}`).then(() => { - setIsModalOpen(false); - setIsLoading(false); - setIter(iter + 1); - }).catch(e => { - setIsLoading(false); - console.log(e); - }); - } - - return ( -
- - -

{subscription.projectArr[0].ownerArr[0].name} / {subscription.projectArr[0].name}

-
- - setIsModalOpen(true)} className="ml-auto">Unsubscribe - -

Are you sure you want to unsubscribe from this project?

-
- Unsubscribe - setIsModalOpen(false)}>Cancel -
-
-
- ); -} \ No newline at end of file diff --git a/components/Tabs.tsx b/components/Tabs.tsx deleted file mode 100644 index 8b48a17..0000000 --- a/components/Tabs.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, {Dispatch, ReactNode, SetStateAction} from "react"; -import {TabInfo} from "../utils/types"; - -export default function Tabs({tabInfo, tab, setTab, className, id}: {tabInfo: TabInfo[], tab: string, setTab: Dispatch>, className?: string, id?: string}) { - return ( -
- {tabInfo.map(thisTab => ( - - ))} -
- ); -} \ No newline at end of file diff --git a/components/UpBanner.tsx b/components/UpBanner.tsx deleted file mode 100644 index efe3fc8..0000000 --- a/components/UpBanner.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, {ReactNode} from "react"; - -export default function UpBanner(props: {children: ReactNode, className?: string}) { - return ( -
- {props.children} -
- ) -} \ No newline at end of file diff --git a/components/UpButton.tsx b/components/UpButton.tsx deleted file mode 100644 index fa199e1..0000000 --- a/components/UpButton.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import {ReactNode} from "react"; -import Link from "next/link"; - -interface UpButtonPropsBase { - children: ReactNode, - small?: boolean, - primary?: boolean, - text?: boolean, - isExtension?: boolean, - className?: string, -} - -interface UpButtonPropsLink extends UpButtonPropsBase { - href: string, - onClick?: never, -} - -interface UpButtonPropsButton extends UpButtonPropsBase { - href?: never, - onClick: () => any, -} - -type UpButtonProps = UpButtonPropsLink | UpButtonPropsButton; - -export default function UpButton({children, isExtension, small, primary, text, href, onClick, className}: UpButtonProps) { - const classNames = `up-button ${small ? "small" : ""} ${primary ? "primary" : ""} ${text ? "text" : ""} ${className || ""}`; - - return href ? isExtension ? ( - {children} - ) : ( - - {children} - - ) : ( - - ); -} \ No newline at end of file diff --git a/components/UpResponsiveH2.tsx b/components/UpResponsiveH2.tsx deleted file mode 100644 index 4d05449..0000000 --- a/components/UpResponsiveH2.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import {ReactNode} from "react"; - -export default function UpResponsiveH2({children, className}: { children: ReactNode, className?: string }) { - return ( -

{children}

- ); -} \ No newline at end of file diff --git a/components/UserSearch.tsx b/components/UserSearch.tsx index 7fcdcb7..9b9ada6 100644 --- a/components/UserSearch.tsx +++ b/components/UserSearch.tsx @@ -1,12 +1,12 @@ -import React, {Dispatch, SetStateAction, useState} from 'react'; +import React, {useState} from "react"; import {DatedObj, UserObj} from "../utils/types"; -import useSWR, {responseInterface} from "swr"; +import useSWR from "swr"; import {fetcher} from "../utils/utils"; import Link from "next/link"; export default function UserSearch() { const [query, setQuery] = useState(""); - const {data, error}: responseInterface<{results: DatedObj[]}, any> = useSWR(`/api/search/user?query=${query}`, query.length ? fetcher : () => []); + const {data} = useSWR<{results: DatedObj[]}>(`/api/search/user?query=${query}`, query.length ? fetcher : async () => []); return ( <> diff --git a/components/back-to-projects.tsx b/components/back-to-projects.tsx deleted file mode 100644 index 735048d..0000000 --- a/components/back-to-projects.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import UpBackLink from "./up-back-link"; - -export default function BackToProjects() { - return ( - - ); -} \ No newline at end of file diff --git a/components/comment-container-item.tsx b/components/comment-container-item.tsx deleted file mode 100644 index 6916f92..0000000 --- a/components/comment-container-item.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, {Dispatch, SetStateAction, useState} from "react"; -import {CommentWithAuthor, DatedObj} from "../utils/types"; -import CommentItem from "./comment-item"; -import {FiChevronDown, FiChevronUp} from "react-icons/fi"; -import Accordion from "react-robust-accordion"; - -export default function CommentContainerItem({iteration, setIteration, comment, subComments}: { - iteration: number, - setIteration: Dispatch>, - comment: DatedObj, - subComments: DatedObj[], -}) { - const [subCommentsOpen, setSubCommentsOpen] = useState(false); - - return ( - <> - - {!!subComments.length && ( -
- -

Show replies ({subComments.length})

-
- {subCommentsOpen ? ( - - ) : ( - - )} -
-
- )}> - {subComments.map(subComment => ( - - ))} - -
- )} - - ); -} \ No newline at end of file diff --git a/components/comment-item.tsx b/components/comment-item.tsx deleted file mode 100644 index cee5ca9..0000000 --- a/components/comment-item.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import {CommentObj, CommentWithAuthor, DatedObj, UserObj} from "../utils/types"; -import Link from "next/link"; -import {format, formatDistanceToNow} from "date-fns"; -import {Dispatch, SetStateAction, useState} from "react"; -import {responseInterface} from "swr"; -import useSWR from "swr"; -import {fetcher} from "../utils/utils"; -import axios from "axios"; -import SpinnerButton from "./spinner-button"; -import Linkify from "react-linkify"; -import {FiCornerDownRight} from "react-icons/all"; -import {useSession} from "next-auth/client"; -import UpModal from "./up-modal"; - -interface PropsBase { - iteration: number, - setIteration: Dispatch>, -} - -interface PropsNew extends PropsBase { - authorId: any, - targetId: string, - parentCommentId?: string, - setParentIsReply?: Dispatch>, - parentIsSubComment?: boolean, - comment?: never, -} - -interface PropsExisting extends PropsBase { - authorId?: never, - targetId?: never, - parentCommentId?: never, - setParentIsReply?: never, - parentIsSubComment?: never, - comment: DatedObj, -} - -export default function CommentItem({ authorId, comment, targetId, parentCommentId, iteration, setIteration, setParentIsReply, parentIsSubComment }: PropsNew | PropsExisting) { - const [session, loading] = useSession(); - const [body, setBody] = useState(comment ? comment.body : ""); - const [commentLoading, setCommentLoading] = useState(false); - const [deleteOpen, setDeleteOpen] = useState(false); - const [deleteLoading, setDeleteLoading] = useState(false); - const [isEdit, setIsEdit] = useState(false); - const [isReply, setIsReply] = useState(false); - - const {data: author, error: authorError}: responseInterface<{ data: DatedObj }, any> = useSWR(`/api/user?id=${authorId || ""}`, authorId ? fetcher : () => null) - - const authorObj = authorId ? ((author && author.data) ? author.data : {name: "", image: "", username: ""}) : comment.author[0]; - const disablePost = !body || (comment ? body === comment.body : false); - const isAuthor = session && comment && session.userId === comment.userId; - - function onSubmit() { - const postData = authorId ? { - targetId: targetId, - parentCommentId: parentCommentId, - body: body, - } : { - id: comment._id, - body: body, - } - - setCommentLoading(true); - - axios.post("/api/comment", postData).then(() => { - setBody(""); - setCommentLoading(false); - setIteration(iteration + 1); - if (setParentIsReply) setParentIsReply(false); - }).catch(e => { - setCommentLoading(false); - console.log(e); - }); - } - - function onDelete() { - setDeleteLoading(true); - - axios.delete("/api/comment", { data: { id: comment ? comment._id : "" } }).then(() => { - setDeleteLoading(false); - setDeleteOpen(false); - setIteration(iteration + 1); - }).catch(e => { - console.log(e); - setDeleteLoading(false); - }) - } - - return ( -
- - - {`Profile - - -
- {(comment && !isEdit) ? ( - <> -
- - {authorObj.name} - - {formatDistanceToNow(new Date(comment.createdAt))} ago -
-

- {comment.body} -

- {isReply ? ( -
- -
- ) : ( -
- {session && ( - - )} - {isAuthor && ( - <> - - - - Are you sure you want to delete this comment? -
- - Delete - - -
-
- - )} -
- )} - - ) : ( - <> -