From 7f2d3f5b6f9abd5a2d958acc5c850ad3799407f0 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Mon, 8 Nov 2021 08:57:49 +0800 Subject: [PATCH 1/3] refactor(profile): contribuite map title style --- src/containers/user/UserProfile/ContributeMap.tsx | 6 +++++- .../user/UserProfile/styles/contribute_map.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/containers/user/UserProfile/ContributeMap.tsx b/src/containers/user/UserProfile/ContributeMap.tsx index 057eb0259..446e3c049 100644 --- a/src/containers/user/UserProfile/ContributeMap.tsx +++ b/src/containers/user/UserProfile/ContributeMap.tsx @@ -13,6 +13,7 @@ import { buildLog } from '@/utils/logger' import { Wrapper, Title, + TitleCount, Header, Divider, DotText, @@ -70,7 +71,10 @@ const UserContributeMap: FC = ({ user }) => { return (
- 过去 1 年共创作 {contributes.totalCount} 次内容 + + 过去 <TitleCount>1</TitleCount> 年共创作{' '} + <TitleCount>{contributes.totalCount}</TitleCount> 次内容 + 潜水   diff --git a/src/containers/user/UserProfile/styles/contribute_map.ts b/src/containers/user/UserProfile/styles/contribute_map.ts index d919b91bd..5c13bbbf8 100644 --- a/src/containers/user/UserProfile/styles/contribute_map.ts +++ b/src/containers/user/UserProfile/styles/contribute_map.ts @@ -28,11 +28,18 @@ export const Divider = styled.div` margin-bottom: 16px; ` export const Title = styled.div` - font-size: 14px; - color: ${theme('thread.articleTitle')}; + ${css.flex('align-center')}; + font-size: 13px; + color: ${theme('thread.articleDigest')}; flex-grow: 1; margin-top: -4px; ` +export const TitleCount = styled.div` + font-size: 13px; + color: ${theme('thread.articleTitle')}; + margin-left: 2px; + margin-right: 3px; +` export const DotText = styled.div` font-size: 12px; color: ${theme('thread.articleDigest')}; From 7ff58edddae8d68bf2a9ac6335122088fcbc4e53 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Mon, 8 Nov 2021 09:37:36 +0800 Subject: [PATCH 2/3] refactor(profile): subscribed community with real data --- src/containers/content/UserContent/store.ts | 2 ++ .../user/UserProfile/NumbersPad.tsx | 18 +++++++------ src/containers/user/UserProfile/index.tsx | 7 ++++-- src/containers/user/UserProfile/store.tsx | 25 +++++++++++++++++-- src/pages/user/[login].tsx | 11 ++++++-- src/widgets/CommunityList/styles/index.ts | 5 +--- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/containers/content/UserContent/store.ts b/src/containers/content/UserContent/store.ts index 9ee7dde28..f247cc3a8 100755 --- a/src/containers/content/UserContent/store.ts +++ b/src/containers/content/UserContent/store.ts @@ -25,6 +25,7 @@ const UserContent = T.model('UserContent', { following: T.optional(T.boolean, false), pagedEditableCommunities: T.optional(PagedCommunities, emptyPagi), + subscribedCommunities: T.optional(PagedCommunities, emptyPagi), pagedWorks: T.optional(PagedWorks, emptyPagi), }) .views((self) => ({ @@ -50,6 +51,7 @@ const UserContent = T.model('UserContent', { get pagedEditableCommunitiesData(): TPagedCommunities { return toJS(self.pagedEditableCommunities) }, + get hasContentBg(): boolean { const root = getParent(self) as TRootStore diff --git a/src/containers/user/UserProfile/NumbersPad.tsx b/src/containers/user/UserProfile/NumbersPad.tsx index 77e315e3f..38cbefc21 100644 --- a/src/containers/user/UserProfile/NumbersPad.tsx +++ b/src/containers/user/UserProfile/NumbersPad.tsx @@ -1,11 +1,10 @@ import { FC, memo } from 'react' -import type { TUser } from '@/spec' +import type { TUser, TPagedCommunities } from '@/spec' import FollowButton from '@/widgets/Buttons/FollowButton' import Button from '@/widgets/Buttons/Button' import { Br } from '@/widgets/Common' import Tooltip from '@/widgets/Tooltip' -import { mockCommunities } from '@/utils/mock' import CommunityList from '@/widgets/CommunityList' @@ -25,9 +24,10 @@ import { type TProps = { user: TUser + subscribedCommunities: TPagedCommunities } -const Numberspad: FC = ({ user }) => { +const Numberspad: FC = ({ user, subscribedCommunities }) => { return ( @@ -56,14 +56,18 @@ const Numberspad: FC = ({ user }) => { - 加入的子社区 + {subscribedCommunities.totalCount === 1 ? ( + 加入的社区 + ) : ( + 加入的子社区 + )} diff --git a/src/containers/user/UserProfile/index.tsx b/src/containers/user/UserProfile/index.tsx index 56f776b8b..cfa9ae49e 100755 --- a/src/containers/user/UserProfile/index.tsx +++ b/src/containers/user/UserProfile/index.tsx @@ -33,11 +33,14 @@ const UserProfileContainer: FC = ({ }) => { useInit(store) - const { viewingUser } = store + const { viewingUser, pagedSubscribedCommunitiesData } = store return ( - + diff --git a/src/containers/user/UserProfile/store.tsx b/src/containers/user/UserProfile/store.tsx index ebad8d21e..1d95dfc0a 100755 --- a/src/containers/user/UserProfile/store.tsx +++ b/src/containers/user/UserProfile/store.tsx @@ -4,11 +4,17 @@ */ import { types as T, getParent, Instance } from 'mobx-state-tree' +import { reject } from 'ramda' + +import type { TRootStore, TUser, TPagedCommunities } from '@/spec' +import { HCN } from '@/constant' -import type { TRootStore, TUser } from '@/spec' import { markStates, toJS } from '@/utils/mobx' +import { PagedCommunities, emptyPagi } from '@/model' -const UserProfile = T.model('UserProfile', {}) +const UserProfile = T.model('UserProfile', { + subscribedCommunities: T.optional(PagedCommunities, emptyPagi), +}) .views((self) => ({ get isLogin(): boolean { const root = getParent(self) as TRootStore @@ -18,6 +24,21 @@ const UserProfile = T.model('UserProfile', {}) const root = getParent(self) as TRootStore return toJS(root.viewing.user) }, + get pagedSubscribedCommunitiesData(): TPagedCommunities { + const { subscribedCommunities } = self + const subscribedCommunitiesData = toJS(subscribedCommunities) + + if (subscribedCommunities.totalCount === 1) { + return subscribedCommunitiesData + } + + const { entries, ...rest } = subscribedCommunitiesData + + return { + entries: reject((c) => c.raw === HCN, entries), + ...rest, + } + }, })) .actions((self) => ({ setViewing(sobj): void { diff --git a/src/pages/user/[login].tsx b/src/pages/user/[login].tsx index 8609dca9b..3b0a26d34 100755 --- a/src/pages/user/[login].tsx +++ b/src/pages/user/[login].tsx @@ -28,10 +28,15 @@ const fetchData = async (context, opt = {}) => { const sessionState = gqClient.request(P.sessionState) const user = gqClient.request(P.user, { login, userHasLogin }) + const filter = { page: 1, size: 10 } + const subscribedCommunities = gqClient.request(P.subscribedCommunities, { + login, + filter, + }) const pagedWorks = gqClient.request(ssrPagedArticleSchema(THREAD.WORKS), { login, - filter: { page: 1, size: 10 }, + filter, userHasLogin, }) @@ -39,6 +44,7 @@ const fetchData = async (context, opt = {}) => { ...(await sessionState), ...(await user), ...(await pagedWorks), + ...(await subscribedCommunities), } } @@ -55,7 +61,7 @@ export const getServerSideProps = async (context) => { return ssrError(context, 'fetch', 500) } - const { user, pagedWorks } = resp + const { user, pagedWorks, subscribedCommunities } = resp const initProps = { ...ssrBaseStates(resp), @@ -63,6 +69,7 @@ export const getServerSideProps = async (context) => { // userContent: { activeThread: query.tab || USER_THREAD.PROFILE }, viewing: { user }, userContent: { pagedWorks }, + userProfile: { subscribedCommunities }, } return { diff --git a/src/widgets/CommunityList/styles/index.ts b/src/widgets/CommunityList/styles/index.ts index 004a1c26e..9a6ebb23f 100755 --- a/src/widgets/CommunityList/styles/index.ts +++ b/src/widgets/CommunityList/styles/index.ts @@ -18,10 +18,7 @@ export const Linker = styled.a` } ` export const Logo = styled(CommunityFaceLogo)<{ size: number }>` - fill: ${theme('thread.articleTitle')}; - width: ${({ size }) => `${size}px`}; - height: ${({ size }) => `${size}px`}; - display: block; + ${({ size }) => `${css.size(size)}`}; ` export const MoreWrapper = styled.div` From d5988488bfb6cf3045e280c59d8e71b994300f25 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Mon, 8 Nov 2021 10:43:13 +0800 Subject: [PATCH 3/3] refactor(profile): tmp activity --- .../user/UserProfile/Activities/BriefInfo.js | 25 -------- .../user/UserProfile/Activities/BriefInfo.tsx | 35 ++++++++++++ .../user/UserProfile/Activities/DetailInfo.js | 22 ------- .../UserProfile/Activities/DetailInfo.tsx | 33 +++++++++++ .../user/UserProfile/Activities/EventBlock.js | 17 ------ .../UserProfile/Activities/EventBlock.tsx | 24 ++++++++ .../user/UserProfile/Activities/index.js | 19 ------- .../user/UserProfile/Activities/index.tsx | 26 +++++++++ src/containers/user/UserProfile/index.tsx | 4 +- src/containers/user/UserProfile/logic.ts | 57 +++++++++++++++++-- src/containers/user/UserProfile/schema.ts | 25 +++----- src/containers/user/UserProfile/store.tsx | 22 ++++++- .../styles/activities/brief_info.ts | 2 +- .../styles/activities/detail_info.ts | 9 ++- .../UserProfile/styles/activities/index.ts | 8 ++- src/spec/index.ts | 1 + src/spec/utils.ts | 9 +++ 17 files changed, 227 insertions(+), 111 deletions(-) delete mode 100644 src/containers/user/UserProfile/Activities/BriefInfo.js create mode 100644 src/containers/user/UserProfile/Activities/BriefInfo.tsx delete mode 100644 src/containers/user/UserProfile/Activities/DetailInfo.js create mode 100644 src/containers/user/UserProfile/Activities/DetailInfo.tsx delete mode 100644 src/containers/user/UserProfile/Activities/EventBlock.js create mode 100644 src/containers/user/UserProfile/Activities/EventBlock.tsx delete mode 100644 src/containers/user/UserProfile/Activities/index.js create mode 100644 src/containers/user/UserProfile/Activities/index.tsx diff --git a/src/containers/user/UserProfile/Activities/BriefInfo.js b/src/containers/user/UserProfile/Activities/BriefInfo.js deleted file mode 100644 index 583f60ac1..000000000 --- a/src/containers/user/UserProfile/Activities/BriefInfo.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' - -import { - Wrapper, - DateInfo, - DayNum, - MonthNum, - Divider, - Desc, -} from '../styles/activities/brief_info' - -const BriefInfo = ({ first }) => { - return ( - - - 03 - / - 5月 - - 讨论了帖子 - - ) -} - -export default React.memo(BriefInfo) diff --git a/src/containers/user/UserProfile/Activities/BriefInfo.tsx b/src/containers/user/UserProfile/Activities/BriefInfo.tsx new file mode 100644 index 000000000..d964adf3e --- /dev/null +++ b/src/containers/user/UserProfile/Activities/BriefInfo.tsx @@ -0,0 +1,35 @@ +import { FC, memo } from 'react' + +import { TUserActivity } from '@/spec' +import { + Wrapper, + DateInfo, + DayNum, + MonthNum, + Divider, + Desc, +} from '../styles/activities/brief_info' + +type TProps = { + activity: TUserActivity + first: boolean +} + +const BriefInfo: FC = ({ activity, first }) => { + const date = new Date(activity.insertedAt) + const month = date.getMonth() + 1 + const day = date.getDate() + + return ( + + + {day} + / + {month}月 + + 发布帖子 + + ) +} + +export default memo(BriefInfo) diff --git a/src/containers/user/UserProfile/Activities/DetailInfo.js b/src/containers/user/UserProfile/Activities/DetailInfo.js deleted file mode 100644 index 209e85d51..000000000 --- a/src/containers/user/UserProfile/Activities/DetailInfo.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -import { ICON } from '@/config' -import { - Wrapper, - Title, - IconWrapper, - Icon, -} from '../styles/activities/detail_info' - -const DetailInfo = ({ first }) => { - return ( - - 这是一篇神奇的帖子 - - - - - ) -} - -export default React.memo(DetailInfo) diff --git a/src/containers/user/UserProfile/Activities/DetailInfo.tsx b/src/containers/user/UserProfile/Activities/DetailInfo.tsx new file mode 100644 index 000000000..556a87cf6 --- /dev/null +++ b/src/containers/user/UserProfile/Activities/DetailInfo.tsx @@ -0,0 +1,33 @@ +import { FC, memo } from 'react' + +import type { TUserActivity } from '@/spec' + +import { + Wrapper, + IconWrapper, + Icon, + Content, + Title, + Digest, +} from '../styles/activities/detail_info' + +type TProps = { + activity: TUserActivity + first: boolean +} + +const DetailInfo: FC = ({ activity, first }) => { + return ( + + + + + + {activity.articleTitle} + {activity.digest} + + + ) +} + +export default memo(DetailInfo) diff --git a/src/containers/user/UserProfile/Activities/EventBlock.js b/src/containers/user/UserProfile/Activities/EventBlock.js deleted file mode 100644 index 9a9bcebc4..000000000 --- a/src/containers/user/UserProfile/Activities/EventBlock.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -import BriefInfo from './BriefInfo' -import DetailInfo from './DetailInfo' - -import { Wrapper } from '../styles/activities/event_block' - -const EventBlock = ({ first }) => { - return ( - - - - - ) -} - -export default React.memo(EventBlock) diff --git a/src/containers/user/UserProfile/Activities/EventBlock.tsx b/src/containers/user/UserProfile/Activities/EventBlock.tsx new file mode 100644 index 000000000..f5577cd97 --- /dev/null +++ b/src/containers/user/UserProfile/Activities/EventBlock.tsx @@ -0,0 +1,24 @@ +import { FC, memo } from 'react' + +import type { TUserActivity } from '@/spec' + +import BriefInfo from './BriefInfo' +import DetailInfo from './DetailInfo' + +import { Wrapper } from '../styles/activities/event_block' + +type TProps = { + activity: TUserActivity + first: boolean +} + +const EventBlock: FC = ({ activity, first }) => { + return ( + + + + + ) +} + +export default memo(EventBlock) diff --git a/src/containers/user/UserProfile/Activities/index.js b/src/containers/user/UserProfile/Activities/index.js deleted file mode 100644 index 41e144bfa..000000000 --- a/src/containers/user/UserProfile/Activities/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' - -import EventBlock from './EventBlock' - -import { Wrapper, Title, Divider } from '../styles/activities' - -const Activities = () => { - return ( - - 最新动态 - - - - - - ) -} - -export default React.memo(Activities) diff --git a/src/containers/user/UserProfile/Activities/index.tsx b/src/containers/user/UserProfile/Activities/index.tsx new file mode 100644 index 000000000..8e5c1765e --- /dev/null +++ b/src/containers/user/UserProfile/Activities/index.tsx @@ -0,0 +1,26 @@ +import { FC, memo } from 'react' + +import type { TUserActivity } from '@/spec' +import EventBlock from './EventBlock' + +import { Wrapper, Title, EmptyHint, Divider } from '../styles/activities' + +type TProps = { + items: TUserActivity[] +} + +const Activities: FC = ({ items }) => { + if (items.length === 0) return 暂无动态 + + return ( + + 最新动态 + + {items.map((activity, index) => ( + + ))} + + ) +} + +export default memo(Activities) diff --git a/src/containers/user/UserProfile/index.tsx b/src/containers/user/UserProfile/index.tsx index cfa9ae49e..b81ed349e 100755 --- a/src/containers/user/UserProfile/index.tsx +++ b/src/containers/user/UserProfile/index.tsx @@ -33,7 +33,7 @@ const UserProfileContainer: FC = ({ }) => { useInit(store) - const { viewingUser, pagedSubscribedCommunitiesData } = store + const { viewingUser, pagedSubscribedCommunitiesData, activities } = store return ( @@ -45,7 +45,7 @@ const UserProfileContainer: FC = ({ - + ) } diff --git a/src/containers/user/UserProfile/logic.ts b/src/containers/user/UserProfile/logic.ts index 77902594b..2304deb52 100755 --- a/src/containers/user/UserProfile/logic.ts +++ b/src/containers/user/UserProfile/logic.ts @@ -1,27 +1,76 @@ import { useEffect } from 'react' // import { } from 'ramda' +import { TYPE, ERR, ARTICLE_THREAD } from '@/constant' +import { errRescue } from '@/utils/helper' import { buildLog } from '@/utils/logger' -// import S from './service' +import asyncSuit from '@/utils/async' + +import S from './schema' import type { TStore } from './store' let store: TStore | undefined +let sub$ = null /* eslint-disable-next-line */ const log = buildLog('L:UserProfile') -export const someMethod = (): void => { - /* todo */ +const { SR71, $solver, asyncRes, asyncErr } = asyncSuit + +const sr71$ = new SR71() + +export const loadPublishedArticles = (): void => { + const { viewingUser: user, isLogin } = store + const filter = { page: 1, size: 30 } + const userHasLogin = isLogin + + store.mark({ resState: TYPE.RES_STATE.LOADING }) + sr71$.query(S.getPagedPublishedArticlesSchema(ARTICLE_THREAD.POST), { + login: user.login, + filter, + userHasLogin, + }) } // ############################### // init & uninit handlers // ############################### +const DataSolver = [ + { + match: asyncRes('pagedPublishedPosts'), + action: ({ pagedPublishedPosts }) => { + console.log('got it: ', pagedPublishedPosts) + store.mark({ pagedPosts: pagedPublishedPosts }) + }, + }, +] + +const ErrSolver = [ + { + match: asyncErr(ERR.GRAPHQL), + action: () => { + // + }, + }, + { + match: asyncErr(ERR.NETWORK), + action: () => { + errRescue({ type: ERR.NETWORK, path: 'PublishedArticles' }) + }, + }, +] + export const useInit = (_store: TStore): void => { useEffect(() => { store = _store log('useInit: ', store) - // return () => store.reset() + sub$ = sr71$.data().subscribe($solver(DataSolver, ErrSolver)) + + loadPublishedArticles() + return () => { + sr71$.stop() + sub$.unsubscribe() + } }, [_store]) } diff --git a/src/containers/user/UserProfile/schema.ts b/src/containers/user/UserProfile/schema.ts index 2ce6cebfc..e1ffec096 100755 --- a/src/containers/user/UserProfile/schema.ts +++ b/src/containers/user/UserProfile/schema.ts @@ -1,23 +1,16 @@ import { gql } from '@urql/core' +import { P } from '@/schemas' -const simpleMutation = gql` - mutation($id: ID!) { - post(id: $id) { - id - } - } -` -const simpleQuery = gql` - query($filter: filter!) { - post(id: $id) { - id - } - } -` +import { titleCase, plural } from '@/utils/helper' + +const getPagedPublishedArticlesSchema = (thread) => { + return gql` + ${P[`pagedPublished${plural(titleCase(thread))}`]} + ` +} const schema = { - simpleMutation, - simpleQuery, + getPagedPublishedArticlesSchema, } export default schema diff --git a/src/containers/user/UserProfile/store.tsx b/src/containers/user/UserProfile/store.tsx index 1d95dfc0a..c93c2af6f 100755 --- a/src/containers/user/UserProfile/store.tsx +++ b/src/containers/user/UserProfile/store.tsx @@ -6,14 +6,20 @@ import { types as T, getParent, Instance } from 'mobx-state-tree' import { reject } from 'ramda' -import type { TRootStore, TUser, TPagedCommunities } from '@/spec' +import type { + TRootStore, + TUser, + TPagedCommunities, + TUserActivity, +} from '@/spec' import { HCN } from '@/constant' import { markStates, toJS } from '@/utils/mobx' -import { PagedCommunities, emptyPagi } from '@/model' +import { PagedCommunities, emptyPagi, PagedPosts } from '@/model' const UserProfile = T.model('UserProfile', { subscribedCommunities: T.optional(PagedCommunities, emptyPagi), + pagedPosts: T.optional(PagedPosts, emptyPagi), }) .views((self) => ({ get isLogin(): boolean { @@ -39,6 +45,18 @@ const UserProfile = T.model('UserProfile', { ...rest, } }, + get activities(): TUserActivity[] { + const slf = self as TStore + const { pagedPosts } = slf + + return toJS(pagedPosts.entries).map((a) => { + return { + articleTitle: a.title, + digest: a.digest, + insertedAt: a.insertedAt, + } + }) + }, })) .actions((self) => ({ setViewing(sobj): void { diff --git a/src/containers/user/UserProfile/styles/activities/brief_info.ts b/src/containers/user/UserProfile/styles/activities/brief_info.ts index fcbbfc2af..cbadd5401 100644 --- a/src/containers/user/UserProfile/styles/activities/brief_info.ts +++ b/src/containers/user/UserProfile/styles/activities/brief_info.ts @@ -32,5 +32,5 @@ export const MonthNum = styled.div` ` export const Desc = styled.div` font-size: 12px; - margin-top: 2px; + margin-top: 3px; ` diff --git a/src/containers/user/UserProfile/styles/activities/detail_info.ts b/src/containers/user/UserProfile/styles/activities/detail_info.ts index 60ba1b724..ba92fd503 100644 --- a/src/containers/user/UserProfile/styles/activities/detail_info.ts +++ b/src/containers/user/UserProfile/styles/activities/detail_info.ts @@ -3,6 +3,7 @@ import styled from 'styled-components' import { theme } from '@/utils/themes' import css from '@/utils/css' import Img from '@/Img' +import EditPenSVG from '@/icons/EditPen' export const Wrapper = styled.div<{ first: boolean }>` position: relative; @@ -23,12 +24,18 @@ export const IconWrapper = styled.div<{ first: boolean }>` ${css.flex('align-both')}; background: #033d45; ` -export const Icon = styled(Img)` +export const Icon = styled(EditPenSVG)` fill: ${theme('thread.articleDigest')}; ${css.size(14)}; ` +export const Content = styled.div`` export const Title = styled.div` color: ${theme('thread.articleTitle')}; font-size: 14px; +` +export const Digest = styled.div` + color: ${theme('thread.articleDigest')}; + ${css.cutRest('400px')}; + font-size: 13px; padding-top: 4px; ` diff --git a/src/containers/user/UserProfile/styles/activities/index.ts b/src/containers/user/UserProfile/styles/activities/index.ts index e00525950..a6f403ac6 100644 --- a/src/containers/user/UserProfile/styles/activities/index.ts +++ b/src/containers/user/UserProfile/styles/activities/index.ts @@ -7,10 +7,14 @@ export const Wrapper = styled.div` margin-top: 50px; ` export const Title = styled.div` - font-size: 14px; - color: ${theme('thread.articleTitle')}; + font-size: 13px; + color: ${theme('thread.articleDigest')}; flex-grow: 1; margin-top: -4px; + padding-left: 3px; +` +export const EmptyHint = styled(Title)` + margin-top: 20px; ` export const Divider = styled.div` width: 100%; diff --git a/src/spec/index.ts b/src/spec/index.ts index db0add680..c0dac7d8e 100644 --- a/src/spec/index.ts +++ b/src/spec/index.ts @@ -67,6 +67,7 @@ export type { TToastType, TToastPos, TView, + TUserActivity, } from './utils' export type { TGQLError } from './graphql' diff --git a/src/spec/utils.ts b/src/spec/utils.ts index bf73fc34d..e84bb998c 100644 --- a/src/spec/utils.ts +++ b/src/spec/utils.ts @@ -148,3 +148,12 @@ export type TCommunitySetterStyle = 'normal' | TTechStackCategory export type TToastType = 'info' | 'error' export type TToastPos = 'topCenter' + +export type TUserActivity = { + id?: TID + insertedAt?: string + articleTitle?: string + digest?: string + action?: string + totalCount?: number +}