From fc3cbcc6038c437d6ff10d0ef2612eb57a48d6a0 Mon Sep 17 00:00:00 2001
From: AkiraFukushima
Date: Sat, 12 Oct 2024 14:46:04 +0900
Subject: [PATCH] refs #4798 Add FollowRequests menu
---
locales/en/translation.json | 5 ++
renderer/components/accounts/New.tsx | 2 +-
renderer/components/layouts/timelines.tsx | 43 ++++++++--
.../components/timelines/FollowRequests.tsx | 75 ++++++++++++++++++
.../timelines/followRequests/User.tsx | 58 ++++++++++++++
renderer/pages/accounts/[id]/[timeline].tsx | 78 ++++++++++++-------
6 files changed, 223 insertions(+), 38 deletions(-)
create mode 100644 renderer/components/timelines/FollowRequests.tsx
create mode 100644 renderer/components/timelines/followRequests/User.tsx
diff --git a/locales/en/translation.json b/locales/en/translation.json
index 01ca5210..3a490b25 100644
--- a/locales/en/translation.json
+++ b/locales/en/translation.json
@@ -6,6 +6,7 @@
"public": "Public",
"favourites": "Favourites",
"bookmarks": "Bookmarks",
+ "follow_requests": "Follow Requests",
"search": "Search",
"status": {
"avatar": "Aavtar of {user}",
@@ -100,6 +101,10 @@
"body": "{user} requested to follow you"
}
},
+ "follow_requests": {
+ "authorize": "Authorize",
+ "reject": "Reject"
+ },
"compose": {
"placeholder": "What's on your mind?",
"spoiler": {
diff --git a/renderer/components/accounts/New.tsx b/renderer/components/accounts/New.tsx
index 1b4aee23..e8bc5772 100644
--- a/renderer/components/accounts/New.tsx
+++ b/renderer/components/accounts/New.tsx
@@ -187,7 +187,7 @@ export default function New(props: NewProps) {
size="sm"
variant="text"
color="blue"
- title={formatMessage({ id: 'accounts.copy_authorization_url' })}
+ title={formatMessage({ id: 'accounts.new.copy_authorization_url' })}
onClick={() => copyText(appData.url)}
>
diff --git a/renderer/components/layouts/timelines.tsx b/renderer/components/layouts/timelines.tsx
index 24d6166a..0d402f2d 100644
--- a/renderer/components/layouts/timelines.tsx
+++ b/renderer/components/layouts/timelines.tsx
@@ -4,7 +4,7 @@ import generator, { Entity, MegalodonInterface } from 'megalodon'
import { useRouter } from 'next/router'
import { createContext, useContext, useEffect, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
-import { FaBell, FaBookmark, FaGlobe, FaHouse, FaList, FaStar, FaUsers, FaHashtag } from 'react-icons/fa6'
+import { FaBell, FaBookmark, FaGlobe, FaHouse, FaList, FaStar, FaUsers, FaHashtag, FaUserPlus } from 'react-icons/fa6'
import { useIntl } from 'react-intl'
import Jump from '../Jump'
import { useUnreads } from '@/provider/unreads'
@@ -37,13 +37,14 @@ export type Timeline = {
export default function Layout({ children }: LayoutProps) {
const router = useRouter()
const { formatMessage } = useIntl()
- const { unreads } = useUnreads()
+ const { unreads, setUnreads } = useUnreads()
const [account, setAccount] = useState(null)
const [lists, setLists] = useState>([])
const [followedTags, setFollowedTags] = useState>([])
const [openJump, setOpenJump] = useState(false)
const [client, setClient] = useState()
+ const [followRequests, setFollowRequests] = useState>([])
useHotkeys('mod+k', () => setOpenJump(current => !current))
@@ -68,10 +69,37 @@ export default function Layout({ children }: LayoutProps) {
}
f()
const g = async () => {
- const res = await c.getFollowedTags()
- setFollowedTags(res.data)
+ try {
+ const res = await c.getFollowedTags()
+ setFollowedTags(res.data)
+ } catch (err) {
+ setFollowedTags([])
+ console.error(err)
+ }
}
g()
+ const r = async () => {
+ const res = await c.getFollowRequests()
+ if (res.data.length > 0) {
+ setUnreads(current =>
+ Object.assign({}, current, {
+ [`${account.id?.toString()}_follow_requests`]: res.data.length
+ })
+ )
+ console.log(unreads)
+ setFollowRequests([
+ {
+ id: 'follow_requests',
+ title: formatMessage({ id: 'timeline.follow_requests' }),
+ icon: ,
+ path: `/accounts/${router.query.id}/follow_requests`
+ }
+ ])
+ } else {
+ setFollowRequests([])
+ }
+ }
+ r()
}, [account])
const pages: Array = [
@@ -127,7 +155,7 @@ export default function Layout({ children }: LayoutProps) {
@{account?.domain}
- {pages.map(page => (
+ {pages.concat(followRequests).map(page => (
{page.icon}
{page.title}
- {page.id === 'notifications' && unreads[account?.id?.toString()] ? (
+ {(page.id === 'notifications' && unreads[account?.id?.toString()]) ||
+ (page.id === 'follow_requests' && unreads[`${account.id.toString()}_follow_requests`]) ? (
, index: number) => void
+}
+
+export default function FollowRequests(props: Props) {
+ const router = useRouter()
+ const { setUnreads } = useUnreads()
+ const [requests, setRequests] = useState>([])
+
+ useEffect(() => {
+ refreshRequests()
+ }, [])
+
+ const refreshRequests = async () => {
+ const res = await props.client.getFollowRequests()
+ setRequests(res.data)
+ return res.data
+ }
+
+ const updateUnreads = (length: number) => {
+ setUnreads(current =>
+ Object.assign({}, current, {
+ [`${props.account.id?.toString()}_follow_requests`]: length
+ })
+ )
+ }
+
+ const timelineClass = () => {
+ if (router.query.detail) {
+ return 'timeline-with-drawer'
+ }
+ return 'timeline'
+ }
+
+ return (
+ <>
+
+
+
+
+ {requests.map(r => (
+ <>
+ {
+ const data = await refreshRequests()
+ updateUnreads(data.length)
+ }}
+ />
+ >
+ ))}
+
+
+
+
+ >
+ )
+}
diff --git a/renderer/components/timelines/followRequests/User.tsx b/renderer/components/timelines/followRequests/User.tsx
new file mode 100644
index 00000000..5ccbca01
--- /dev/null
+++ b/renderer/components/timelines/followRequests/User.tsx
@@ -0,0 +1,58 @@
+import emojify from '@/utils/emojify'
+import { Avatar } from '@material-tailwind/react'
+import { Entity, MegalodonInterface } from 'megalodon'
+import { useRouter } from 'next/router'
+import { FaCheck, FaXmark } from 'react-icons/fa6'
+import { useIntl } from 'react-intl'
+
+type Props = {
+ user: Entity.Account | Entity.FollowRequest
+ client: MegalodonInterface
+ refresh: () => Promise
+}
+
+export default function User(props: Props) {
+ const router = useRouter()
+ const { formatMessage } = useIntl()
+
+ const openUser = (id: string) => {
+ router.push({ query: { id: router.query.id, timeline: router.query.timeline, user_id: id, detail: true } })
+ }
+
+ const authorize = async () => {
+ await props.client.acceptFollowRequest(`${props.user.id}`)
+ await props.refresh()
+ }
+
+ const reject = async () => {
+ await props.client.rejectFollowRequest(`${props.user.id}`)
+ await props.refresh()
+ }
+
+ return (
+
+
+
openUser(`${props.user.id}`)}>
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/renderer/pages/accounts/[id]/[timeline].tsx b/renderer/pages/accounts/[id]/[timeline].tsx
index 8b3eaab3..3df9d5c1 100644
--- a/renderer/pages/accounts/[id]/[timeline].tsx
+++ b/renderer/pages/accounts/[id]/[timeline].tsx
@@ -7,6 +7,7 @@ import Notifications from '@/components/timelines/Notifications'
import Search from '@/components/timelines/Search'
import Media from '@/components/Media'
import Report from '@/components/report/Report'
+import FollowRequests from '@/components/timelines/FollowRequests'
export default function Page() {
const router = useRouter()
@@ -58,39 +59,56 @@ export default function Page() {
})
}
+ const timeline = (timeline: string) => {
+ switch (timeline) {
+ case 'notifications':
+ return (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ )
+ case 'search':
+ return (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ )
+ case 'follow_requests':
+ return (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ )
+ default:
+ return (
+ , index: number) =>
+ dispatch({ target: 'media', value: true, object: media, index: index })
+ }
+ />
+ )
+ }
+ }
+
if (!account || !client) return null
return (
<>
- {(router.query.timeline as string) === 'notifications' ? (
- , index: number) =>
- dispatch({ target: 'media', value: true, object: media, index: index })
- }
- />
- ) : (
- <>
- {(router.query.timeline as string) === 'search' ? (
- , index: number) =>
- dispatch({ target: 'media', value: true, object: media, index: index })
- }
- />
- ) : (
- , index: number) =>
- dispatch({ target: 'media', value: true, object: media, index: index })
- }
- />
- )}
- >
- )}
+ {timeline(router.query.timeline as string)}
dispatch({ target: 'media', value: false, object: [], index: -1 })}