mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-26 10:44:20 +01:00
feat: new nostr-settings profile, scroll to newly created reply
This commit is contained in:
@@ -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 >
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"> • 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"> • 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"> • 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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user