Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add swipe delete on messages #54

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/build

# misc
/.idea
.DS_Store
*.pem

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"react-icons": "^4.4.0",
"react-markdown": "^8.0.3",
"react-query": "^3.39.1",
"react-swipe-to-delete-ios": "^2.0.71",
"reflect-metadata": "^0.1.13",
"remark-gfm": "^3.0.1",
"type-graphql": "^1.1.1",
Expand Down
168 changes: 110 additions & 58 deletions src/pages/inbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useQuery, useMutation, dehydrate } from 'react-query';
import { getSession, useSession } from 'next-auth/react';
import { IoReload } from 'react-icons/io5';
Expand All @@ -9,10 +9,12 @@ import { useRouter } from 'next/router';
import toast from 'react-hot-toast';
import Image from 'next/image';

import { BiMessageDots } from 'react-icons/bi';
import SwipeToDelete from 'react-swipe-to-delete-ios';
import { Info } from '@/components';
import { useLogEvent } from '@/hooks';
import type { Message } from '@/generated/graphql';
import { editMessage, getMessages, queryClient } from '@/api';
import { deleteMessage, editMessage, getMessages, queryClient } from '@/api';
import { MessageDialog, SettingsDialog } from '@/components/Dialog';

const Inbox = () => {
Expand All @@ -21,6 +23,7 @@ const Inbox = () => {
const [msgModal, setMsgModal] = useState(false);
const [settingsModal, setSettingsModal] = useState(false);
const [messageData, setMessageData] = useState({} as Partial<Message>);
const state = useRef({ x: 0 });

const { data, status } = useSession();
const { id, username } = data?.user ?? {};
Expand All @@ -40,26 +43,40 @@ const Inbox = () => {

const { mutate } = useMutation(editMessage);

const handleOpen = (data: Partial<Message>) => {
setMessageData(data);

if (data.id && !data.isOpened) {
mutate(
{
id: data.id,
isOpened: true,
},
{
onSuccess: () => {
refetch();
},
}
);
}
setMsgModal(true);
triggerEvent('open_message');
const handleMouseDown = (e: { screenX: number }) => {
state.current.x = e.screenX;
};

const handleOpen =
(data: Partial<Message>) =>
(e: { screenX: number; stopPropagation: () => void }) => {
setMessageData(data);

const delta = Math.abs(e.screenX - state.current.x);

if (delta < 10) {
e.stopPropagation();

if (data.id && !data.isOpened) {
mutate(
{
id: data.id,
isOpened: true,
},
{
onSuccess: () => {
refetch();
},
}
);
}
setMsgModal(true);
triggerEvent('open_message');
}
};

const handleDelete = useMutation(deleteMessage);

const copyLink = () => {
navigator.clipboard.writeText(`https://umamin.link/to/${username}`);
toast.success('Copied to clipboard');
Expand Down Expand Up @@ -108,52 +125,87 @@ const Inbox = () => {
</div>

<div className='my-10 w-full text-left'>
<div className='mb-5 flex flex-col'>
<div className='flex justify-between'>
<p className='font-medium'>
{messages?.length ? 'Latest messages' : 'No messages to show'}
</p>
<button type='button' onClick={() => refetch()}>
<IoReload
className={`text-lg ${
isLoading || isRefetching ? 'animate-spin' : ''
}`}
/>
{messages?.length ? (
<div className='mb-5 flex flex-col'>
<div className='flex justify-between'>
<p className='font-medium'>Latest messages</p>
<button type='button' onClick={() => refetch()}>
<IoReload
className={`text-lg ${
isLoading || isRefetching ? 'animate-spin' : ''
}`}
/>
</button>
</div>
<Info
className='text-xs'
message='Tap a card to reveal an anonymous message or swipe left to delete.'
/>
</div>
) : (
<div className='mb-5 text-center'>
<BiMessageDots className='mx-auto text-3xl' />
<p className='font-medium'>No messages yet.</p>
<button
type='button'
onClick={() => refetch()}
className='text-xs text-primary-100'
>
Tap to refresh
</button>
</div>
<Info message='Tap a card to reveal an anonymous message.' />
</div>
)}

<div className='space-y-6'>
{messages?.map((m) => (
<button
type='button'
<SwipeToDelete
key={m.id}
onClick={() => handleOpen(m)}
className='msg-card hide-tap-highlight w-full cursor-pointer overflow-hidden text-left'
onDelete={() => {
handleDelete.mutate(
{ id: m.id },
{
onSuccess: () => {
toast.success('Message deleted');
refetch();
},
}
);
}}
height={180}
deleteWidth={100}
deleteColor='transparent'
className='my-auto'
>
<div className='relative mb-3 h-[40px]'>
<Image
src='/assets/logo.svg'
layout='fill'
objectFit='contain'
/>
</div>

<div className='send chat-p flex max-w-full items-center space-x-3 bg-secondary-100 px-6 py-4 font-medium before:bg-secondary-100 after:bg-secondary-200'>
<p className='reply text-secondary-400'>{m.receiverMsg}</p>
</div>
<div
className={
m.isOpened
? 'flex items-center justify-end space-x-1 text-right text-sm font-medium italic text-secondary-400'
: 'hidden'
}
<button
type='button'
key={m.id}
onMouseDown={handleMouseDown}
onClick={handleOpen(m)}
className='msg-card hide-tap-highlight h-[180px] w-full cursor-pointer overflow-hidden text-left'
>
<p>Seen</p>
<BsCheck2 className='text-base' />
</div>
</button>
<div className='relative mb-3 h-[40px]'>
<Image
src='/assets/logo.svg'
layout='fill'
objectFit='contain'
/>
</div>

<div className='send chat-p flex max-w-full items-center space-x-3 bg-secondary-100 px-6 py-4 font-medium before:bg-secondary-100 after:bg-secondary-200'>
<p className='reply text-secondary-400'>{m.receiverMsg}</p>
</div>
<div
className={
m.isOpened
? 'flex items-center justify-end space-x-1 text-right text-sm font-medium italic text-secondary-400'
: 'hidden'
}
>
<p>Seen</p>
<BsCheck2 className='text-base' />
</div>
</button>
</SwipeToDelete>
))}
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6843,6 +6843,11 @@ react-query@^3.39.1:
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"

react-swipe-to-delete-ios@^2.0.71:
version "2.0.71"
resolved "https://registry.yarnpkg.com/react-swipe-to-delete-ios/-/react-swipe-to-delete-ios-2.0.71.tgz#96342e7ff2b06797b88d15e0012d0ca6777ad5dd"
integrity sha512-+lhd+Xhs7Wqn3nLb1V4o/AS+qW8k+U9iVKErt74nmUOAJyUlqmaWraHP9V9hr2XjzisUq8JtJq+OHbkkLxDdkA==

react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Expand Down