From d21770d0f713bcdc89c0044780df73700db1bf51 Mon Sep 17 00:00:00 2001 From: Ourai Lin Date: Tue, 11 Mar 2025 17:58:39 +0800 Subject: [PATCH] feat(app): use DevPlaza design --- src/app/u/[handle]/creator/page.js | 6 +- src/domain/profile/repository.js | 6 +- .../profile/views/team-profile/DevPlaza.tsx | 59 ++++++++++++++++ .../views/team-profile/LatestActivityList.js | 44 ------------ .../profile/views/team-profile/TeamProfile.js | 70 +++++++------------ .../activity-tab-list/ActivityTabList.js | 31 ++++---- .../control/block-editor/typing.ts} | 18 +---- 7 files changed, 106 insertions(+), 128 deletions(-) create mode 100644 src/domain/profile/views/team-profile/DevPlaza.tsx delete mode 100644 src/domain/profile/views/team-profile/LatestActivityList.js rename src/shared/{hooks/useIsMobile.js => components/control/block-editor/typing.ts} (55%) diff --git a/src/app/u/[handle]/creator/page.js b/src/app/u/[handle]/creator/page.js index 286b5cf5..2274b39c 100644 --- a/src/app/u/[handle]/creator/page.js +++ b/src/app/u/[handle]/creator/page.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import { fetchUser, fetchUserActivityList } from '#/domain/profile/repository'; +import { fetchUser } from '#/domain/profile/repository'; import ProjectOwner from './ProjectOwner'; @@ -25,7 +25,5 @@ export default async function CreatorProfile({ params }) { return
This user is not a creator.
; } - const { data: activityData } = await fetchUserActivityList(data?.base.user_id); - - return ; + return ; } diff --git a/src/domain/profile/repository.js b/src/domain/profile/repository.js index 4f117cee..6cf78e85 100644 --- a/src/domain/profile/repository.js +++ b/src/domain/profile/repository.js @@ -44,10 +44,6 @@ async function updateUser(data) { return httpClient.post('/user/info', data); } -async function fetchUserActivityList(uid) { - return httpClient.get(`/user/info/${uid}/creator/activity`); -} - function resolvePaginationParams(params) { return { ...params, take: 20 }; } @@ -88,7 +84,7 @@ async function updateBlockContent(data) { } export { - fetchUser, updateUser, fetchUserActivityList, + fetchUser, updateUser, fetchFollowerList, fetchFollowedList, followUser, unfollowUser, updateBanner, fetchBlockContent, updateBlockContent, diff --git a/src/domain/profile/views/team-profile/DevPlaza.tsx b/src/domain/profile/views/team-profile/DevPlaza.tsx new file mode 100644 index 00000000..1c925006 --- /dev/null +++ b/src/domain/profile/views/team-profile/DevPlaza.tsx @@ -0,0 +1,59 @@ +/** + * Copyright 2024 OpenBuild + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import { useDebouncedCallback } from 'use-debounce'; + +import { isBlockDataValid } from '@/components/control/block-editor/helper'; +import useMounted from '@/hooks/useMounted'; + +import type { JSONContent } from '@/components/control/block-editor/typing'; + +import { useViewingSelf } from '../../../auth/hooks'; +import { fetchBlockContent, updateBlockContent } from '../../repository'; +import CustomContent from './CustomContent'; + +function DevPlaza({ params }: { params?: { userId: number } }) { + const [blockContent, setBlockContent] = useState(null); + const viewingSelf = useViewingSelf(params?.userId); + + useMounted(() => { + fetchBlockContent(params?.userId).then(res => { + if (res.success) { + setBlockContent(res.data); + } + }); + }); + + const handleBlockChange = useDebouncedCallback(updateBlockContent, 3000); + + const rerenderKey = [ + 'CustomContent', + `${viewingSelf ? 'editable' : 'readonly'}`, + isBlockDataValid(blockContent), + ].join('-'); + + return ( + + ); +} + +export default DevPlaza; diff --git a/src/domain/profile/views/team-profile/LatestActivityList.js b/src/domain/profile/views/team-profile/LatestActivityList.js deleted file mode 100644 index 60bc6d01..00000000 --- a/src/domain/profile/views/team-profile/LatestActivityList.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright 2024 OpenBuild - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { formatTime } from '@/utils/date'; - -const typeTextMap = { - open_course: 'Open Course', - bounty: 'Bounty', - challenges: 'Challanges', - quiz: 'Quiz', -}; - -function LatestActivityList({ activities }) { - return ( -
- {activities?.map(i => ( -
- {/* */} - {i.type !== '' &&

{typeTextMap[i.type]}

} -
{i.title}
-

Posted on {formatTime(i.created_at * 1000)}

-
- ))} -
- ); -} - -export default LatestActivityList; diff --git a/src/domain/profile/views/team-profile/TeamProfile.js b/src/domain/profile/views/team-profile/TeamProfile.js index 19ba8cd5..4bd0efdc 100644 --- a/src/domain/profile/views/team-profile/TeamProfile.js +++ b/src/domain/profile/views/team-profile/TeamProfile.js @@ -15,26 +15,20 @@ */ import { useState } from 'react'; -import { useDebouncedCallback } from 'use-debounce'; -import { isBlockDataValid } from '@/components/control/block-editor/helper'; import useAppConfig from '@/hooks/useAppConfig'; -import useMounted from '@/hooks/useMounted'; -import { useViewingSelf } from '../../../auth/hooks'; import PublishedBountyListView from '../../../bounty/views/published-bounty-list'; import PublishedChallengeListView from '../../../challenge/views/published-challenge-list'; import PublishedCourseListView from '../../../course/views/published-course-list'; import PublishedQuizListView from '../../../quiz/views/published-quiz-list'; -import { fetchBlockContent, updateBlockContent } from '../../repository'; import ActivityTabListWidget from '../../widgets/activity-tab-list'; import SocialInfoWidget from '../../widgets/social-info'; import TabBarWidget from '../../widgets/tab-bar'; -import CustomContent from './CustomContent'; -import LatestActivityList from './LatestActivityList'; +import DevPlaza from './DevPlaza'; -function resolveTabs(published) { - return [ +function resolveTabs(published, extraTabs = []) { + return [].concat(extraTabs, [ { text: 'Open Course', node: ( @@ -75,56 +69,44 @@ function resolveTabs(published) { ), view: PublishedQuizListView, }, - ]; + ]); }; -function TeamProfileView({ data, activities }) { - const [tabActive, setTabActive] = useState(1); - const [blockContent, setBlockContent] = useState(null); - const viewingSelf = useViewingSelf(data?.base.user_id); +function TeamProfileView({ data }) { + const [tabActive, setTabActive] = useState(0); const devPlazaEnabled = useAppConfig('devPlaza.enabled'); - useMounted(() => { - devPlazaEnabled && - fetchBlockContent(data?.base.user_id).then(res => { - if (res.success) { - setBlockContent(res.data); - } - }); - }); - - const handleBlockChange = useDebouncedCallback(updateBlockContent, 3000); - const tabContent = [ , - , ]; - const rerenderKey = [ - 'CustomContent', - `${viewingSelf ? 'editable' : 'readonly'}`, - isBlockDataValid(blockContent), - ].join('-'); + const extraTabs = []; + + if (devPlazaEnabled) { + extraTabs.push({ + text: 'DevPlaza', + node: ( + <> + DevPlaza + DevPlaza + + ), + view: DevPlaza, + filterable: false, + }); + } return (
- {devPlazaEnabled && ( - - )} - {tabContent[tabActive]} - +
{tabContent[tabActive]}
+
); } diff --git a/src/domain/profile/widgets/activity-tab-list/ActivityTabList.js b/src/domain/profile/widgets/activity-tab-list/ActivityTabList.js index 1a05abf7..03aaae3c 100644 --- a/src/domain/profile/widgets/activity-tab-list/ActivityTabList.js +++ b/src/domain/profile/widgets/activity-tab-list/ActivityTabList.js @@ -29,30 +29,33 @@ function ActivityTabList({ userId, tabs }) { const [activity, setActivity] = useState(0); const [sort, setSort] = useState(); - const ActivityList = tabs[activity].view; + const activeTab = tabs[activity]; + const ActivityList = activeTab.view; const params = { userId, sort: sort?.value }; return ( <> -
+
node ? { text, node } : text)} tabClassName="h-14 md:h-9 md:!px-6" current={activity} onChange={setActivity} /> -
- -
+ {activeTab.filterable !== false && ( +
+ +
+ )}
{ActivityList && } diff --git a/src/shared/hooks/useIsMobile.js b/src/shared/components/control/block-editor/typing.ts similarity index 55% rename from src/shared/hooks/useIsMobile.js rename to src/shared/components/control/block-editor/typing.ts index 40ec7e07..3a967ee5 100644 --- a/src/shared/hooks/useIsMobile.js +++ b/src/shared/components/control/block-editor/typing.ts @@ -14,20 +14,4 @@ * limitations under the License. */ -import { useEffect, useState } from 'react'; - -export function useIsMobile() { - const [isMobile, setIsMobile] = useState(false); - useEffect(() => { - if ( - window.navigator.userAgent.match( - /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i - ) - ) { - setIsMobile(true); - } else { - setIsMobile(false); - } - }, []); - return isMobile; -} +export type { JSONContent } from 'novel';