feat: new nostr-settings profile, scroll to newly created reply

This commit is contained in:
MTG2000
2022-07-30 16:57:54 +03:00
parent d43b3215bb
commit bb08a158ff
6 changed files with 109 additions and 60 deletions

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react'
import { IoIosCopy } from 'react-icons/io'
import CopyToClipboardComponent from 'react-copy-to-clipboard';
import { motion } from 'framer-motion'
import { FiCopy } from 'react-icons/fi';
interface Props {
text: string;
@@ -40,7 +41,7 @@ export default function CopyToClipboard({ text, direction = 'bottom', iconClasse
return (
<>
<CopyToClipboardComponent text={text} onCopy={handleCopy}>
<IoIosCopy className={`input-icon hover:cursor-pointer ${iconClasses}`} />
<FiCopy className={`input-icon hover:cursor-pointer transition-transform hover:scale-110 active:scale-90 ${iconClasses}`} />
</CopyToClipboardComponent>
<motion.div
variants={variants}
@@ -50,7 +51,7 @@ export default function CopyToClipboard({ text, direction = 'bottom', iconClasse
if (showAlert)
setTimeout(() => setShowAlert(false), 2000)
}}
className={`absolute rounded-xl text-center bg-black text-white w-full p-16 ${className} ${alertClasses}`}>Copied to clipboard <IoIosCopy className='align-middle' />
className={`absolute rounded-xl text-center bg-black text-white right-0 z-10 p-16 ${className} ${alertClasses}`}>Copied to clipboard <IoIosCopy className='align-middle' />
</motion.div >
</>
)

View File

@@ -62,6 +62,7 @@ export default function VoteButton({
hideVotesCoun = false,
dense = false,
resetCounterOnRelease = true,
onSuccess,
...props }: Props) {
const [voteCnt, setVoteCnt] = useState(0)
const voteCntRef = useRef(0);
@@ -83,7 +84,7 @@ export default function VoteButton({
onSuccess: (amount) => {
setBtnState("success");
spawnSparks(10);
props.onSuccess?.(amount);
onSuccess?.(amount);
},
onError: () => setBtnState('fail'),
onSetteled: () => {

View File

@@ -1,6 +1,6 @@
import { useToggle } from "@react-hookz/web";
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import Button from "src/Components/Button/Button";
import { useAppSelector } from "src/utils/hooks";
@@ -21,7 +21,22 @@ export default function Comment({ comment, canReply, isRoot, onClickedReply, onR
const [replyOpen, setReplyOpen] = useState(false);
const [repliesCollapsed, toggleRepliesCollapsed] = useToggle(true)
const user = useAppSelector(s => s.user.me)
const [scrollToLatestReply, setScrollToLatestReply] = useState(true);
const repliesContainer = useRef<HTMLDivElement>(null!)
const user = useAppSelector(s => s.user.me);
useEffect(() => {
if (repliesCollapsed)
setReplyOpen(false);
}, [repliesCollapsed])
useEffect(() => {
if (scrollToLatestReply) {
repliesContainer.current?.querySelector(`:scope > div:nth-child(${comment.replies.length})`)?.scrollIntoView({ behavior: 'smooth', block: "center" })
setScrollToLatestReply(false);
}
}, [comment.replies.length, scrollToLatestReply])
const clickReply = () => {
if (isRoot)
@@ -35,6 +50,7 @@ export default function Comment({ comment, canReply, isRoot, onClickedReply, onR
await onReply?.(text);
toggleRepliesCollapsed(false);
setReplyOpen(false);
setScrollToLatestReply(true)
return true;
} catch (error) {
return false;
@@ -55,18 +71,23 @@ export default function Comment({ comment, canReply, isRoot, onClickedReply, onR
<span className="text-gray-600"><span className="align-middle">Hide replies</span> <FaChevronUp className="ml-12" /> </span>
}
</Button>}
{!repliesCollapsed && comment.replies.map(reply => <Comment
key={reply.id}
comment={reply}
onClickedReply={clickReply}
canReply={!!isRoot}
/>)}
{replyOpen && <AddComment
avatar={user?.avatar!}
autoFocus
placeholder="Leave a reply..."
onSubmit={handleReply}
/>}
<div
className="flex flex-col gap-16 w-full"
ref={repliesContainer}
>
{!repliesCollapsed && comment.replies.map(reply => <Comment
key={reply.id}
comment={reply}
onClickedReply={clickReply}
canReply={!!isRoot}
/>)}
{replyOpen && <AddComment
avatar={user?.avatar!}
autoFocus
placeholder="Leave a reply..."
onSubmit={handleReply}
/>}
</div>
</div>
</div>}
</div>

View File

@@ -19,19 +19,6 @@ export default function CommentCard({ comment, canReply, onReply }: Props) {
const [votesCount, setVotesCount] = useState(comment.votes_count);
// const onVote: ComponentProps<typeof VoteButton>['onVote'] = async (amount, config) => {
// try {
// const pr = await lightningAddressToPR(comment.author?.lightning_address ?? CONSTS.defaultLightningAddress, amount);
// const webln = await Wallet_Service.getWebln()
// const paymentResponse = await webln.sendPayment(pr);
// config.onSuccess?.()
// } catch (error) {
// config.onError?.()
// } finally {
// config.onSetteled?.();
// }
// }
const { vote } = useVote({
itemId: comment.id,
itemType: Vote_Item_Type.PostComment,

View File

@@ -61,17 +61,20 @@ export default function CommentsSection({ type, id }: Props) {
return (
<div className="border-2 border-gray-200 rounded-12 md:rounded-16 p-32 bg-white">
<div className="flex flex-wrap justify-between">
<h6 className="text-body2 font-bolder">Discussion</h6>
{connectionStatus.status === 'Connected' && <div className="bg-green-50 text-green-500 text-body5 font-medium py-4 px-12 rounded-48"> &#8226; Connected to {connectionStatus.connectedRelaysCount} relays 📡</div>}
{connectionStatus.status === 'Connecting' && <div className="bg-amber-50 text-amber-500 text-body5 font-medium py-4 px-12 rounded-48"> &#8226; Connecting to relays </div>}
{connectionStatus.status === 'Not Connected' && <div className="bg-red-50 text-red-500 text-body5 font-medium py-4 px-12 rounded-48"> &#8226; Not connected 📡</div>}
</div>
{showTooltip && <div className="bg-gray-900 text-white p-16 rounded-12 my-24 flex items-center justify-between gap-12">
{showTooltip && <div className="bg-gray-900 text-white p-16 rounded-12 my-24 flex items-center justify-between gap-8 md:gap-12">
<span>💬</span>
<p className="text-body4 font-medium">Learn about <Link to={createRoute({ type: "story", title: "What is Nostr", id: 999 })} className='underline'>how your data is stored</Link> with Nostr comments and relays</p>
<IconButton className='shrink-0 self-start' onClick={closeTooltip}><AiOutlineClose className='text-gray-600' /></IconButton>
</div>}
{!!user && <div className="mt-24">
<AddComment
placeholder='Leave a comment...'
@@ -79,6 +82,7 @@ export default function CommentsSection({ type, id }: Props) {
avatar={user.avatar}
/>
</div>}
<div className='flex flex-col gap-16 mt-32'>
{commentsTree.map(comment =>
<CommentRoot

View File

@@ -1,7 +1,12 @@
import { useToggle } from '@react-hookz/web'
import { FaChevronDown } from 'react-icons/fa';
import { Nullable } from 'remirror';
import Button from 'src/Components/Button/Button';
import CopyToClipboard from 'src/Components/CopyToClipboard/CopyToClipboard';
import IconButton from 'src/Components/IconButton/IconButton';
import { CONSTS } from 'src/utils';
import { motion } from "framer-motion";
interface Props {
isOwner?: boolean;
@@ -12,53 +17,83 @@ interface Props {
export default function CommentsSettingsCard({ nostr_prv_key, nostr_pub_key, isOwner }: Props) {
const [relaysDropdownOpen, toggleRelaysDropdownOpen] = useToggle(false)
return (
<div className="rounded-16 bg-white border-2 border-gray-200 p-24">
<p className="text-body2 font-bold">Nostr Settings (experimental)</p>
<p className="text-body2 font-bold">💬 Nostr comments <span className="bg-green-50 text-green-500 text-body5 font-medium py-4 px-12 rounded-48 ml-8">Experimental</span></p>
<p className="mt-8 text-body4 text-gray-600">
Our commenting system is experimental and uses Nostr to store and relay your messages and replies to our own relay, as well as relays ran by other people in the community.
We generate Nostr keys for you since there are no popular wallets which support it.
</p>
{nostr_prv_key && <>
<p className="text-body4 font-bold mt-24">
Your Nostr Private Key
</p>
<div className="input-wrapper mt-8 relative">
<input
type={'text'}
className="input-text"
value={nostr_prv_key}
/>
</div>
</>}
<p className="text-body4 font-bold mt-24">
Your Nostr Public Key
</p>
<div className="input-wrapper mt-8 relative">
<input
<div className='mt-24 flex flex-col gap-16'>
<p className="text-body3 font-bold">Nostr keys</p>
{nostr_prv_key && <div>
<p className="text-body5 font-bold">
Your Nostr Private Key
</p>
<div className="input-wrapper mt-8 relative">
<input
type={'password'}
className="input-text"
value={nostr_prv_key}
/>
<CopyToClipboard text={nostr_prv_key} />
</div>
</div>}
<div>
<p className="text-body5 font-bold">
Your Nostr Public Key
</p>
<div className="input-wrapper mt-8 relative">
<input
type='text'
className="input-text"
value={nostr_pub_key!}
/>
<CopyToClipboard text={nostr_pub_key ?? ''} />
</div>
</div>
type='text'
className="input-text"
value={nostr_pub_key!}
/>
</div>
<p className="text-body4 font-bold mt-24">
{/* <p className="text-body4 font-bold mt-24">
Connect your Nostr identity
</p>
<div className="mt-8 py-12 relative">
<p className="text-body4 text-gray-400 font-bold">
🚧 Coming Soon 🚧
</p>
</div> */}
<div className='mt-24'>
<div className="flex justify-between">
<p className="text-body4 font-bold">
Nostr relays
</p>
<IconButton onClick={() => toggleRelaysDropdownOpen()}>
<motion.div
animate={{ rotate: relaysDropdownOpen ? 180 : 0 }}
>
<FaChevronDown />
</motion.div>
</IconButton>
</div>
{relaysDropdownOpen &&
<motion.ul
initial={{ y: '-50%', opacity: 0 }}
animate={{ y: '0', opacity: 1 }}
className="mt-8 relative flex flex-col gap-8">
{CONSTS.DEFAULT_RELAYS.map((url, idx) => <li key={idx} className="text-body4 border-b py-12 px-16 border border-gray-200 rounded-16">{url}</li>)}
</motion.ul>}
</div>
<p className="text-body4 font-bold mt-24">
Connected Relays
</p>
<ul className="mt-8 relative flex flex-col gap-8">
{CONSTS.DEFAULT_RELAYS.map((url, idx) => <li key={idx} className="text-body4 border-b py-12 px-16 bg-gray-100 border-2 border-gray-200 rounded-16">{url}</li>)}
</ul>
<Button color='gray' fullWidth disabled className='mt-24'>
Connect your Nostr ID (coming soon)
</Button>
</div>
)
}