Skip to content

Commit

Permalink
feat(profile): make my recent clippings can fully load from server
Browse files Browse the repository at this point in the history
  • Loading branch information
AnnatarHe committed Nov 8, 2023
1 parent a814d76 commit 25ce11a
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 41 deletions.
16 changes: 10 additions & 6 deletions src/app/dash/[userid]/profile/bind-phone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { toast } from 'react-hot-toast'
import BindPhone from '../../../../components/bind-phone'
import Dialog from '../../../../components/dialog/dialog'
import { useBindUserPhoneMutation } from '../../../../schema/generated'
import { Button, Tooltip } from '@mantine/core'
import { DevicePhoneMobileIcon } from '@heroicons/react/24/outline'

type BindPhoneProps = {
}
Expand All @@ -29,14 +31,16 @@ function ProfileBindPhone(props: BindPhoneProps) {
setVisible(false)
}, [doAuthResponse])


return (
<React.Fragment>
<button
className='text-2xl ml-4 p-2 transform transition-all hover:scale-110 duration-300 hover:bg-blue-400 hover:bg-opacity-50 focus:outline-none'
onClick={() => setVisible(true)}
title={t('app.profile.phoneBind') ?? ''}
> {t('app.profile.phoneBind')} </button>
<Tooltip label={t('app.profile.phoneBind')}>
<Button
bg={'transparent'}
onClick={() => setVisible(true)}
>
<DevicePhoneMobileIcon className='w-6 h-6' />
</Button>
</Tooltip>
{visible && (
<Dialog
title={t('app.profile.editor.title')}
Expand Down
17 changes: 11 additions & 6 deletions src/app/dash/[userid]/profile/cli-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Dialog from '../../../../components/dialog/dialog'
import { CodeHighlight } from '@mantine/code-highlight';
import { getLocalToken } from '../../../../services/ajax'
import { useClaimCliApiTokenMutation } from '../../../../schema/generated'
import { Button, Tooltip } from '@mantine/core';

type CliApiTokenProps = {
}
Expand Down Expand Up @@ -51,12 +52,16 @@ ck-cli

return (
<div>
<CommandLineIcon
onClick={() => {
setVisible(true)
}}
className='w-12 h-12 text-2xl ml-4 p-2 transform transition-all hover:scale-110 duration-300 hover:bg-blue-400 hover:bg-opacity-50 focus:outline-none cursor-pointer'
/>
<Tooltip label={'CLI'}>
<Button
onClick={() => {
setVisible(true)
}}
bg={'transparent'}
>
<CommandLineIcon className='w-6 h-6' />
</Button>
</Tooltip>
{visible && (
<Dialog
onCancel={() => {
Expand Down
98 changes: 98 additions & 0 deletions src/app/dash/[userid]/profile/clipping-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import { FetchClippingsByUidQuery, useFetchClippingsByUidQuery } from '../../../../schema/generated'
import { Masonry, useInfiniteLoader } from 'masonic'
import { useMasonaryColumnCount } from '../../../../hooks/use-screen-size'
import ClippingItem from '../../../../components/clipping-item/clipping-item'
import { IN_APP_CHANNEL } from '../../../../services/channel'
import { useMultipBook } from '../../../../hooks/book'

type ClippingListProps = {
uid: number
userDomain: string
}

function ClippingList(props: ClippingListProps) {
const { uid, userDomain } = props
const [renderList, setRenderList] = React.useState<FetchClippingsByUidQuery['clippingList']['items']>([])
const { data, loading, fetchMore } = useFetchClippingsByUidQuery({
variables: {
uid,
pagination: {
limit: 20,
}
},
onCompleted(data) {
setRenderList(data.clippingList.items)
},
})

const masonaryColumnCount = useMasonaryColumnCount()
const maybeLoadMore = useInfiniteLoader((startIndex, stopIndex, currentItems: FetchClippingsByUidQuery['clippingList']['items']) => {
if (renderList.length === 0 || !data) {
return
}
if (currentItems.length >= data.clippingList.count) {
return Promise.reject(1)
}
return fetchMore({
variables: {
uid,
pagination: {
limit: 10,
lastId: currentItems[currentItems.length - 1].id
}
},
}).then((resp) => {
setRenderList(prev => [...prev, ...resp.data.clippingList.items].reduce((acc, cur) => {
if (!acc.find(x => x.id === cur.id)) {
acc.push(cur)
}
return acc
}, [] as FetchClippingsByUidQuery['clippingList']['items']))
})
}, {
isItemLoaded: (index, items) => !!items[index],
threshold: 3,
totalItems: data?.clippingList.count ?? 0
})

const books = useMultipBook(data?.clippingList.items.map(x => x.bookID) ?? [])

if (loading && !data) {
return (
<div className='grid grid-cols-3 gap-4'>
{new Array(9).fill(0).map((_, index) => (
<div
key={index}
className='w-full h-52 animate-pulse bg-gray-300 dark:bg-gray-800'
/>
))}
</div>
)
}

return (
<Masonry
items={renderList}
columnCount={masonaryColumnCount}
columnGutter={30}
onRender={maybeLoadMore}
itemKey={(x) => x.id}
render={(row) => {
const clipping = row.data
return (
<ClippingItem
item={clipping}
domain={userDomain || uid.toString()}
book={books.books.find(x => x.doubanId.toString() === clipping.bookID)}
creator={clipping.creator}
key={clipping.id}
inAppChannel={IN_APP_CHANNEL.clippingFromBook}
/>
)
}}
/>
)
}

export default ClippingList
21 changes: 8 additions & 13 deletions src/app/dash/[userid]/profile/content.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client';
import React, { useMemo, useState } from 'react'
import React, { Suspense, useMemo, useState } from 'react'
import ClippingItem from '../../../../components/clipping-item/clipping-item';
import { usePageTrack, useTitle } from '../../../../hooks/tracke'
import WechatBindButton from './bind';
Expand All @@ -23,6 +23,9 @@ import { ProfileQuery, useFollowUserMutation, useUnfollowUserMutation, useUpdate
import { Divider, Text } from '@mantine/core';
import UserName from '../../../../components/profile/user-name';
import styles from './profile.module.css'
import { Masonry } from 'masonic';
import ClippingList from './clipping-list';
import Loading from '../../../loading';

type ProfilePageContentProps = {
userid: string
Expand Down Expand Up @@ -181,18 +184,10 @@ function ProfilePageContent(props: ProfilePageContentProps) {
className='my-8'
/>

<MasonryContainer className='anna-fade-in'>
<React.Fragment>
{data.me.recents.map(
(item => <ClippingItem
key={item.id}
item={item}
domain={data.me.domain.length > 2 ? data.me.domain : data.me.id.toString()}
inAppChannel={IN_APP_CHANNEL.clippingFromUser}
/>)
)}
</React.Fragment>
</MasonryContainer>
<Suspense fallback={<Loading />}>
<ClippingList uid={uid} userDomain={data?.me.domain} />
</Suspense>

{isPickingAvatar && (
<AvatarPicker
onCancel={() => setIsPickingAvatar(false)}
Expand Down
17 changes: 11 additions & 6 deletions src/app/dash/[userid]/profile/profile-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { uploadImage } from '../../../../services/misc'
import { useTranslation } from 'react-i18next'
import ExternalAccountList from '../../../../components/externalAccount/list'
import { useUpdateProfileMutation } from '../../../../schema/generated'
import { Button } from '@mantine/core'
import { Button, Tooltip } from '@mantine/core'
import { CogIcon } from '@heroicons/react/24/solid'

type ProfileEditorProps = {
uid: number
Expand Down Expand Up @@ -100,11 +101,15 @@ function ProfileEditor(props: ProfileEditorProps) {

return (
<React.Fragment>
<button
className='text-2xl ml-4 p-2 transform transition-all hover:scale-110 duration-300 hover:bg-blue-400 hover:bg-opacity-50 focus:outline-none'
onClick={() => setVisible(true)}
title={t('app.profile.editor.title') ?? ''}
></button>
<Tooltip label={t('app.profile.editor.title')}>
<Button
onClick={() => setVisible(true)}
bg='transparent'
title={t('app.profile.editor.title') ?? ''}
>
<CogIcon className='w-6 h-6' />
</Button>
</Tooltip>
{visible && (
<Dialog
title={t('app.profile.editor.title')}
Expand Down
8 changes: 5 additions & 3 deletions src/components/clipping-item/clipping-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ import Link from 'next/link';
import { Clipping, User } from '../../schema/generated';

type TClippingItemProps = {
className?: string
item: Pick< Clipping, 'id' | 'bookID' | 'title' | 'content'>
book?: WenquBook
domain: string
inAppChannel: IN_APP_CHANNEL
creator?: Pick<User, 'avatar' | 'id' | 'name' | 'clippingsCount'>
}

function ClippingItem({ domain, item, book, creator, inAppChannel }: TClippingItemProps) {
function ClippingItem(props: TClippingItemProps) {
const { domain, item, book, creator, inAppChannel, className = '' } = props
const { t } = useTranslation()
return (
<Link
href={`/dash/${domain}/clippings/${item.id}?iac=${inAppChannel}`}
key={item.id}
className='block mx-2 md:mx-0'>

className={'block mx-2 md:mx-0 ' + className}
>
<Card className={styles.clipping + ' lg:p-10 p-2 hover:shadow-2xl transition-all duration-300'} style={{ margin: '1rem 0' }}>
<>
<h3 className='lg:text-3xl text-xl font-lxgw'>
Expand Down
4 changes: 2 additions & 2 deletions src/components/index-page/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ function Hero(props: HeroProps) {
<div className='flex items-end my-6'>
<Link
href={goLinkUrl}
className={'w-fit py-4 px-12 text-6xl rounded-lg block font-extrabold text-white hover:shadow-2xl bg-gradient-to-br from-blue-500 to-purple-700 text-center'}>
className={'w-fit py-4 px-12 text-6xl rounded-lg block font-extrabold text-white hover:shadow-2xl bg-gradient-to-br from-blue-500 to-purple-700 text-center active:scale-95'}>
{t('app.go')}
</Link>

{AppFeatures.enablePremiumPayment && (
<Button
component={Link}
href='/pricing'
className='text-xl ml-4'
className='text-xl ml-4 transition-all active:scale-95 duration-150'
rightSection={<ChevronRightIcon className='w-4 h-4' />}
>
😎 {t('app.plan.premium.name')}
Expand Down
4 changes: 2 additions & 2 deletions src/components/navigation-bar/authed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ function LoggedNavigationBar(props: LoggedNavigationBarProps) {
onClick={onSearch}
className=' bg-slate-500 bg-opacity-50 backdrop-blur hover:bg-opacity-100 transition-all duration-150 flex items-center px-4 py-2 rounded-full text-slate-800 dark:text-white'
>
<MagnifyingGlassIcon className='w-6 h-6' />
<span className='ml-2'>{t('app.menu.search.title')}</span>
<MagnifyingGlassIcon className='w-6 h-6 text-slate-100 dark:text-slate-300' />
<span className='ml-2 text-slate-100 dark:text-slate-300'>{t('app.menu.search.title')}</span>
</button>
</li>
<li className='mr-6'>
Expand Down
4 changes: 1 addition & 3 deletions src/components/searchbar/searchbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function SearchBar(props: SearchBarProps) {
closeOnEscape
centered
withCloseButton={false}
className='dark:bg-gray-900 bg-gray-100'
className='dark:bg-slate-900 bg-slate-100'
>
<Input
onChange={onInputChange}
Expand Down Expand Up @@ -128,9 +128,7 @@ function SearchBar(props: SearchBarProps) {
href={`/dash/${profile.domain.length > 3 ? profile.domain : profile.id}/clippings/${c.id}`}
className='block py-8 px-4'
onClick={props.onClose}>

<p className='text-xl leading-normal'>{c.content}</p>

</Link>
</li>
))}
Expand Down
20 changes: 20 additions & 0 deletions src/schema/clippingsByUid.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
query fetchClippingsByUid($uid: Int!, $pagination: Pagination!) {
clippingList(uid: $uid, pagination: $pagination) {
count
items {
id
title
content
bookID
pageAt
creator {
id
avatar
clippingsCount
name
domain
}
}
}
}

Loading

0 comments on commit 25ce11a

Please sign in to comment.