cleanups
This commit is contained in:
parent
06162e4f66
commit
3d3ee3a087
13 changed files with 332 additions and 238 deletions
18
src/App.jsx
18
src/App.jsx
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import Header from './components/Header';
|
||||
import Home from './pages/Home';
|
||||
import Login from './pages/Login';
|
||||
import Mikroblog from './pages/Mikroblog';
|
||||
import Profile from './pages/Profile';
|
||||
import AdminPanel from './pages/AdminPanel';
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Header from "./components/Header";
|
||||
import Home from "./pages/Home";
|
||||
import Login from "./pages/Login";
|
||||
import Mikroblog from "./pages/Mikroblog";
|
||||
import Profile from "./pages/Profile";
|
||||
import AdminPanel from "./pages/AdminPanel";
|
||||
// import Settings from './pages/Settings';
|
||||
import './styles/main.scss';
|
||||
import "./styles/main.scss";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LogIn, LogOut, Home, MessageSquare, Settings, Shield } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
LogIn,
|
||||
LogOut,
|
||||
Home,
|
||||
MessageSquare,
|
||||
Settings,
|
||||
Shield,
|
||||
} from "lucide-react";
|
||||
import { useStore } from "../store/useStore";
|
||||
|
||||
function Header() {
|
||||
const { publicKey, profile, logout } = useStore();
|
||||
|
@ -24,7 +31,7 @@ function Header() {
|
|||
<div className="header__nav">
|
||||
{publicKey ? (
|
||||
<>
|
||||
{(profile?.role === 'admin' || profile?.role === 'moderator') && (
|
||||
{(profile?.role === "admin" || profile?.role === "moderator") && (
|
||||
<Link to="/admin">
|
||||
<Shield size={16} />
|
||||
Panel
|
||||
|
@ -33,7 +40,7 @@ function Header() {
|
|||
<Link to="/settings">
|
||||
<Settings size={20} />
|
||||
</Link>
|
||||
<button onClick={logout} className="button">
|
||||
<button type="button" onClick={logout} className="button">
|
||||
<LogOut size={20} />
|
||||
</button>
|
||||
</>
|
||||
|
|
10
src/main.jsx
10
src/main.jsx
|
@ -1,9 +1,9 @@
|
|||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
</StrictMode>,
|
||||
);
|
|
@ -1,23 +1,40 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Shield, User, MessageSquare, Ban, CheckCircle, XCircle } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import { fetchAllPosts, fetchAllUsers, updateUserRole, banUser, removePost } from '../utils/nostr';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { pl } from 'date-fns/locale';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Shield,
|
||||
User,
|
||||
MessageSquare,
|
||||
Ban,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { useStore } from "../store/useStore";
|
||||
import {
|
||||
fetchAllPosts,
|
||||
fetchAllUsers,
|
||||
updateUserRole,
|
||||
banUser,
|
||||
removePost,
|
||||
} from "../utils/nostr";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { pl } from "date-fns/locale";
|
||||
|
||||
function AdminPanel() {
|
||||
const navigate = useNavigate();
|
||||
const { publicKey, profile } = useStore();
|
||||
const [activeTab, setActiveTab] = useState('users');
|
||||
const [activeTab, setActiveTab] = useState("users");
|
||||
const [users, setUsers] = useState([]);
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [processingAction, setProcessingAction] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!publicKey || !profile || (profile.role !== 'admin' && profile.role !== 'moderator')) {
|
||||
navigate('/');
|
||||
if (
|
||||
!publicKey ||
|
||||
!profile ||
|
||||
(profile.role !== "admin" && profile.role !== "moderator")
|
||||
) {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -29,12 +46,12 @@ function AdminPanel() {
|
|||
try {
|
||||
const [fetchedUsers, fetchedPosts] = await Promise.all([
|
||||
fetchAllUsers(),
|
||||
fetchAllPosts()
|
||||
fetchAllPosts(),
|
||||
]);
|
||||
setUsers(fetchedUsers);
|
||||
setPosts(fetchedPosts);
|
||||
} catch (error) {
|
||||
console.error('Error loading admin data:', error);
|
||||
console.error("Error loading admin data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -43,42 +60,42 @@ function AdminPanel() {
|
|||
async function handleUpdateRole(userId, newRole) {
|
||||
if (processingAction[userId]) return;
|
||||
|
||||
setProcessingAction(prev => ({ ...prev, [userId]: true }));
|
||||
setProcessingAction((prev) => ({ ...prev, [userId]: true }));
|
||||
try {
|
||||
await updateUserRole(userId, newRole);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
console.error("Error updating user role:", error);
|
||||
} finally {
|
||||
setProcessingAction(prev => ({ ...prev, [userId]: false }));
|
||||
setProcessingAction((prev) => ({ ...prev, [userId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBanUser(userId, isBanned) {
|
||||
if (processingAction[userId]) return;
|
||||
|
||||
setProcessingAction(prev => ({ ...prev, [userId]: true }));
|
||||
setProcessingAction((prev) => ({ ...prev, [userId]: true }));
|
||||
try {
|
||||
await banUser(userId, isBanned);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
console.error('Error updating user ban status:', error);
|
||||
console.error("Error updating user ban status:", error);
|
||||
} finally {
|
||||
setProcessingAction(prev => ({ ...prev, [userId]: false }));
|
||||
setProcessingAction((prev) => ({ ...prev, [userId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemovePost(postId) {
|
||||
if (processingAction[postId]) return;
|
||||
|
||||
setProcessingAction(prev => ({ ...prev, [postId]: true }));
|
||||
setProcessingAction((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await removePost(postId);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
console.error('Error removing post:', error);
|
||||
console.error("Error removing post:", error);
|
||||
} finally {
|
||||
setProcessingAction(prev => ({ ...prev, [postId]: false }));
|
||||
setProcessingAction((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,28 +109,30 @@ function AdminPanel() {
|
|||
<div className="admin-panel__header">
|
||||
<h1>
|
||||
<Shield size={24} />
|
||||
Panel {profile?.role === 'admin' ? 'Administratora' : 'Moderatora'}
|
||||
Panel {profile?.role === "admin" ? "Administratora" : "Moderatora"}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="tabs">
|
||||
<button
|
||||
className={`tab ${activeTab === 'users' ? 'tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('users')}
|
||||
type="button"
|
||||
className={`tab ${activeTab === "users" ? "tab--active" : ""}`}
|
||||
onClick={() => setActiveTab("users")}
|
||||
>
|
||||
<User size={16} />
|
||||
Użytkownicy
|
||||
</button>
|
||||
<button
|
||||
className={`tab ${activeTab === 'posts' ? 'tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('posts')}
|
||||
type="button"
|
||||
className={`tab ${activeTab === "posts" ? "tab--active" : ""}`}
|
||||
onClick={() => setActiveTab("posts")}
|
||||
>
|
||||
<MessageSquare size={16} />
|
||||
Wpisy
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{activeTab === 'users' ? (
|
||||
{activeTab === "users" ? (
|
||||
<div className="admin-panel__users">
|
||||
<table className="table">
|
||||
<thead>
|
||||
|
@ -125,8 +144,11 @@ function AdminPanel() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map(user => (
|
||||
<tr key={user.id} className={user.banned ? 'table__row--banned' : ''}>
|
||||
{users.map((user) => (
|
||||
<tr
|
||||
key={user.id}
|
||||
className={user.banned ? "table__row--banned" : ""}
|
||||
>
|
||||
<td>
|
||||
<div className="table__user">
|
||||
{user.picture ? (
|
||||
|
@ -140,7 +162,7 @@ function AdminPanel() {
|
|||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{user.role || 'user'}</td>
|
||||
<td>{user.role || "user"}</td>
|
||||
<td>
|
||||
{user.banned ? (
|
||||
<span className="badge badge--danger">
|
||||
|
@ -156,10 +178,12 @@ function AdminPanel() {
|
|||
</td>
|
||||
<td>
|
||||
<div className="table__actions">
|
||||
{profile?.role === 'admin' && (
|
||||
{profile?.role === "admin" && (
|
||||
<select
|
||||
value={user.role || 'user'}
|
||||
onChange={(e) => handleUpdateRole(user.id, e.target.value)}
|
||||
value={user.role || "user"}
|
||||
onChange={(e) =>
|
||||
handleUpdateRole(user.id, e.target.value)
|
||||
}
|
||||
disabled={processingAction[user.id]}
|
||||
>
|
||||
<option value="user">Użytkownik</option>
|
||||
|
@ -168,7 +192,8 @@ function AdminPanel() {
|
|||
</select>
|
||||
)}
|
||||
<button
|
||||
className={`button ${user.banned ? 'button--success' : 'button--danger'}`}
|
||||
type="button"
|
||||
className={`button ${user.banned ? "button--success" : "button--danger"}`}
|
||||
onClick={() => handleBanUser(user.id, !user.banned)}
|
||||
disabled={processingAction[user.id]}
|
||||
>
|
||||
|
@ -203,17 +228,22 @@ function AdminPanel() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{posts.map(post => (
|
||||
{posts.map((post) => (
|
||||
<tr key={post.id}>
|
||||
<td>
|
||||
<div className="table__user">
|
||||
{post.author.picture ? (
|
||||
<img src={post.author.picture} alt={post.author.name} />
|
||||
<img
|
||||
src={post.author.picture}
|
||||
alt={post.author.name}
|
||||
/>
|
||||
) : (
|
||||
<User size={24} />
|
||||
)}
|
||||
<div>
|
||||
<strong>{post.author.name || post.author.id.slice(0, 8)}</strong>
|
||||
<strong>
|
||||
{post.author.name || post.author.id.slice(0, 8)}
|
||||
</strong>
|
||||
<small>{post.author.id}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -222,24 +252,21 @@ function AdminPanel() {
|
|||
<div className="table__content">
|
||||
<p>{post.content}</p>
|
||||
<div className="table__meta">
|
||||
<span>
|
||||
{post.votes.up - post.votes.down} punktów
|
||||
</span>
|
||||
<span>{post.votes.up - post.votes.down} punktów</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{post.comments} komentarzy
|
||||
</span>
|
||||
<span>{post.comments} komentarzy</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{formatDistanceToNow(post.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
className="button button--danger"
|
||||
onClick={() => handleRemovePost(post.id)}
|
||||
disabled={processingAction[post.id]}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import { fetchPosts, publishPost, vote, fetchComments } from '../utils/nostr';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { pl } from 'date-fns/locale';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from "lucide-react";
|
||||
import { useStore } from "../store/useStore";
|
||||
import { fetchPosts, publishPost, vote, fetchComments } from "../utils/nostr";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { pl } from "date-fns/locale";
|
||||
|
||||
function Home() {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [newPost, setNewPost] = useState('');
|
||||
const [newPost, setNewPost] = useState("");
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const [votingStates, setVotingStates] = useState({});
|
||||
const [expandedComments, setExpandedComments] = useState({});
|
||||
|
@ -26,7 +26,7 @@ function Home() {
|
|||
const fetchedPosts = await fetchPosts();
|
||||
setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt));
|
||||
} catch (error) {
|
||||
console.error('Error loading posts:', error);
|
||||
console.error("Error loading posts:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -39,10 +39,10 @@ function Home() {
|
|||
setPublishing(true);
|
||||
try {
|
||||
await publishPost(newPost, privateKey);
|
||||
setNewPost('');
|
||||
setNewPost("");
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error publishing post:', error);
|
||||
console.error("Error publishing post:", error);
|
||||
} finally {
|
||||
setPublishing(false);
|
||||
}
|
||||
|
@ -51,14 +51,14 @@ function Home() {
|
|||
async function handleVote(postId, postAuthor, isUpvote) {
|
||||
if (!privateKey || votingStates[postId]) return;
|
||||
|
||||
setVotingStates(prev => ({ ...prev, [postId]: true }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await vote(postId, postAuthor, isUpvote, privateKey);
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
console.error("Error voting:", error);
|
||||
} finally {
|
||||
setVotingStates(prev => ({ ...prev, [postId]: false }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,30 +66,30 @@ function Home() {
|
|||
if (!expandedComments[postId]) {
|
||||
try {
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: true }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: true }));
|
||||
} catch (error) {
|
||||
console.error('Error fetching comments:', error);
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
} else {
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: false }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePublishComment(postId) {
|
||||
if (!newComments[postId]?.trim() || !privateKey) return;
|
||||
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: true }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await publishPost(newComments[postId], privateKey, postId);
|
||||
setNewComments(prev => ({ ...prev, [postId]: '' }));
|
||||
setNewComments((prev) => ({ ...prev, [postId]: "" }));
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error publishing comment:', error);
|
||||
console.error("Error publishing comment:", error);
|
||||
} finally {
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: false }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ function Home() {
|
|||
className="button"
|
||||
>
|
||||
<Send size={20} />
|
||||
{publishing ? 'Publikowanie...' : 'Opublikuj'}
|
||||
{publishing ? "Publikowanie..." : "Opublikuj"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -130,7 +130,8 @@ function Home() {
|
|||
<div key={post.id} className="post">
|
||||
<div className="post__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, true)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -138,7 +139,8 @@ function Home() {
|
|||
</button>
|
||||
<span>{post.votes.up - post.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, false)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -153,11 +155,12 @@ function Home() {
|
|||
<span>
|
||||
{formatDistanceToNow(post.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleExpandComments(post.id)}
|
||||
className="button button--link"
|
||||
>
|
||||
|
@ -171,40 +174,54 @@ function Home() {
|
|||
{publicKey && (
|
||||
<div className="comments__form">
|
||||
<textarea
|
||||
value={newComments[post.id] || ''}
|
||||
onChange={(e) => setNewComments(prev => ({
|
||||
value={newComments[post.id] || ""}
|
||||
onChange={(e) =>
|
||||
setNewComments((prev) => ({
|
||||
...prev,
|
||||
[post.id]: e.target.value
|
||||
}))}
|
||||
[post.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Napisz komentarz..."
|
||||
className="post-input"
|
||||
rows="2"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handlePublishComment(post.id)}
|
||||
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()}
|
||||
disabled={
|
||||
publishingComments[post.id] ||
|
||||
!newComments[post.id]?.trim()
|
||||
}
|
||||
className="button"
|
||||
>
|
||||
<Reply size={16} />
|
||||
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'}
|
||||
{publishingComments[post.id]
|
||||
? "Wysyłanie..."
|
||||
: "Odpowiedz"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comments[post.id]?.map(comment => (
|
||||
{comments[post.id]?.map((comment) => (
|
||||
<div key={comment.id} className="comment">
|
||||
<div className="comment__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, true)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, true)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowUp size={16} />
|
||||
</button>
|
||||
<span>{comment.votes.up - comment.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, false)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, false)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowDown size={16} />
|
||||
|
@ -218,7 +235,7 @@ function Home() {
|
|||
<span>
|
||||
{formatDistanceToNow(comment.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||
import { useStore } from '../store/useStore';
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { generateSecretKey, getPublicKey } from "nostr-tools";
|
||||
import { useStore } from "../store/useStore";
|
||||
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
|
@ -14,9 +14,9 @@ function Login() {
|
|||
const privateKey = generateSecretKey();
|
||||
const publicKey = getPublicKey(privateKey);
|
||||
setKeys(publicKey, privateKey);
|
||||
navigate('/');
|
||||
navigate("/");
|
||||
} catch (error) {
|
||||
console.error('Error generating keys:', error);
|
||||
console.error("Error generating keys:", error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
@ -26,11 +26,12 @@ function Login() {
|
|||
<h2>Zaloguj się</h2>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleGenerateKeys}
|
||||
disabled={loading}
|
||||
className="button button--full"
|
||||
>
|
||||
{loading ? 'Generowanie...' : 'Wygeneruj nowy klucz'}
|
||||
{loading ? "Generowanie..." : "Wygeneruj nowy klucz"}
|
||||
</button>
|
||||
<p className="text-light">
|
||||
Twój klucz prywatny zostanie bezpiecznie zapisany w przeglądarce
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import { fetchPosts, publishPost, vote, fetchComments } from '../utils/nostr';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { pl } from 'date-fns/locale';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from "lucide-react";
|
||||
import { useStore } from "../store/useStore";
|
||||
import { fetchPosts, publishPost, vote, fetchComments } from "../utils/nostr";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { pl } from "date-fns/locale";
|
||||
|
||||
function Mikroblog() {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [newPost, setNewPost] = useState('');
|
||||
const [newPost, setNewPost] = useState("");
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const [votingStates, setVotingStates] = useState({});
|
||||
const [expandedComments, setExpandedComments] = useState({});
|
||||
|
@ -23,10 +23,10 @@ function Mikroblog() {
|
|||
|
||||
async function loadPosts() {
|
||||
try {
|
||||
const fetchedPosts = await fetchPosts('mikroblog');
|
||||
const fetchedPosts = await fetchPosts("mikroblog");
|
||||
setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt));
|
||||
} catch (error) {
|
||||
console.error('Error loading posts:', error);
|
||||
console.error("Error loading posts:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -37,17 +37,17 @@ function Mikroblog() {
|
|||
if (!newPost.trim() || !privateKey) return;
|
||||
|
||||
if (newPost.length > 280) {
|
||||
alert('Wpis nie może być dłuższy niż 280 znaków!');
|
||||
alert("Wpis nie może być dłuższy niż 280 znaków!");
|
||||
return;
|
||||
}
|
||||
|
||||
setPublishing(true);
|
||||
try {
|
||||
await publishPost(newPost, privateKey, null, 'mikroblog');
|
||||
setNewPost('');
|
||||
await publishPost(newPost, privateKey, null, "mikroblog");
|
||||
setNewPost("");
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error publishing post:', error);
|
||||
console.error("Error publishing post:", error);
|
||||
} finally {
|
||||
setPublishing(false);
|
||||
}
|
||||
|
@ -56,14 +56,14 @@ function Mikroblog() {
|
|||
async function handleVote(postId, postAuthor, isUpvote) {
|
||||
if (!privateKey || votingStates[postId]) return;
|
||||
|
||||
setVotingStates(prev => ({ ...prev, [postId]: true }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await vote(postId, postAuthor, isUpvote, privateKey);
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
console.error("Error voting:", error);
|
||||
} finally {
|
||||
setVotingStates(prev => ({ ...prev, [postId]: false }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +71,13 @@ function Mikroblog() {
|
|||
if (!expandedComments[postId]) {
|
||||
try {
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: true }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: true }));
|
||||
} catch (error) {
|
||||
console.error('Error fetching comments:', error);
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
} else {
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: false }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,21 +85,21 @@ function Mikroblog() {
|
|||
if (!newComments[postId]?.trim() || !privateKey) return;
|
||||
|
||||
if (newComments[postId].length > 280) {
|
||||
alert('Komentarz nie może być dłuższy niż 280 znaków!');
|
||||
alert("Komentarz nie może być dłuższy niż 280 znaków!");
|
||||
return;
|
||||
}
|
||||
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: true }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await publishPost(newComments[postId], privateKey, postId, 'mikroblog');
|
||||
setNewComments(prev => ({ ...prev, [postId]: '' }));
|
||||
await publishPost(newComments[postId], privateKey, postId, "mikroblog");
|
||||
setNewComments((prev) => ({ ...prev, [postId]: "" }));
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
await loadPosts();
|
||||
} catch (error) {
|
||||
console.error('Error publishing comment:', error);
|
||||
console.error("Error publishing comment:", error);
|
||||
} finally {
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: false }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,9 +121,7 @@ function Mikroblog() {
|
|||
rows="3"
|
||||
maxLength={280}
|
||||
/>
|
||||
<span className="character-count">
|
||||
{newPost.length}/280
|
||||
</span>
|
||||
<span className="character-count">{newPost.length}/280</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -131,7 +129,7 @@ function Mikroblog() {
|
|||
className="button"
|
||||
>
|
||||
<Send size={20} />
|
||||
{publishing ? 'Publikowanie...' : 'Opublikuj'}
|
||||
{publishing ? "Publikowanie..." : "Opublikuj"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -146,7 +144,8 @@ function Mikroblog() {
|
|||
<div key={post.id} className="post">
|
||||
<div className="post__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, true)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -154,7 +153,8 @@ function Mikroblog() {
|
|||
</button>
|
||||
<span>{post.votes.up - post.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, false)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -169,11 +169,12 @@ function Mikroblog() {
|
|||
<span>
|
||||
{formatDistanceToNow(post.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleExpandComments(post.id)}
|
||||
className="button button--link"
|
||||
>
|
||||
|
@ -188,45 +189,59 @@ function Mikroblog() {
|
|||
<div className="comments__form">
|
||||
<div className="post-input-wrapper">
|
||||
<textarea
|
||||
value={newComments[post.id] || ''}
|
||||
onChange={(e) => setNewComments(prev => ({
|
||||
value={newComments[post.id] || ""}
|
||||
onChange={(e) =>
|
||||
setNewComments((prev) => ({
|
||||
...prev,
|
||||
[post.id]: e.target.value
|
||||
}))}
|
||||
[post.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Napisz komentarz... (max 280 znaków)"
|
||||
className="post-input"
|
||||
rows="2"
|
||||
maxLength={280}
|
||||
/>
|
||||
<span className="character-count">
|
||||
{(newComments[post.id] || '').length}/280
|
||||
{(newComments[post.id] || "").length}/280
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handlePublishComment(post.id)}
|
||||
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()}
|
||||
disabled={
|
||||
publishingComments[post.id] ||
|
||||
!newComments[post.id]?.trim()
|
||||
}
|
||||
className="button"
|
||||
>
|
||||
<Reply size={16} />
|
||||
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'}
|
||||
{publishingComments[post.id]
|
||||
? "Wysyłanie..."
|
||||
: "Odpowiedz"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comments[post.id]?.map(comment => (
|
||||
{comments[post.id]?.map((comment) => (
|
||||
<div key={comment.id} className="comment">
|
||||
<div className="comment__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, true)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, true)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowUp size={16} />
|
||||
</button>
|
||||
<span>{comment.votes.up - comment.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, false)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, false)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowDown size={16} />
|
||||
|
@ -240,7 +255,7 @@ function Mikroblog() {
|
|||
<span>
|
||||
{formatDistanceToNow(comment.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply, User, Calendar } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import { fetchUserPosts, fetchUserProfile, publishPost, vote, fetchComments } from '../utils/nostr';
|
||||
import { formatDistanceToNow, format } from 'date-fns';
|
||||
import { pl } from 'date-fns/locale';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import {
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
MessageSquare,
|
||||
Send,
|
||||
Reply,
|
||||
User,
|
||||
Calendar,
|
||||
} from "lucide-react";
|
||||
import { useStore } from "../store/useStore";
|
||||
import {
|
||||
fetchUserPosts,
|
||||
fetchUserProfile,
|
||||
publishPost,
|
||||
vote,
|
||||
fetchComments,
|
||||
} from "../utils/nostr";
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { pl } from "date-fns/locale";
|
||||
|
||||
function Profile() {
|
||||
const { pubkey } = useParams();
|
||||
|
@ -17,7 +31,7 @@ function Profile() {
|
|||
const [newComments, setNewComments] = useState({});
|
||||
const [publishingComments, setPublishingComments] = useState({});
|
||||
const { publicKey, privateKey } = useStore();
|
||||
const [activeTab, setActiveTab] = useState('all');
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
|
||||
useEffect(() => {
|
||||
loadProfileData();
|
||||
|
@ -28,12 +42,12 @@ function Profile() {
|
|||
try {
|
||||
const [userProfile, userPosts] = await Promise.all([
|
||||
fetchUserProfile(pubkey),
|
||||
fetchUserPosts(pubkey)
|
||||
fetchUserPosts(pubkey),
|
||||
]);
|
||||
setProfile(userProfile);
|
||||
setPosts(userPosts.sort((a, b) => b.createdAt - a.createdAt));
|
||||
} catch (error) {
|
||||
console.error('Error loading profile data:', error);
|
||||
console.error("Error loading profile data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -42,14 +56,14 @@ function Profile() {
|
|||
async function handleVote(postId, postAuthor, isUpvote) {
|
||||
if (!privateKey || votingStates[postId]) return;
|
||||
|
||||
setVotingStates(prev => ({ ...prev, [postId]: true }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await vote(postId, postAuthor, isUpvote, privateKey);
|
||||
await loadProfileData();
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
console.error("Error voting:", error);
|
||||
} finally {
|
||||
setVotingStates(prev => ({ ...prev, [postId]: false }));
|
||||
setVotingStates((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,13 +71,13 @@ function Profile() {
|
|||
if (!expandedComments[postId]) {
|
||||
try {
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: true }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: true }));
|
||||
} catch (error) {
|
||||
console.error('Error fetching comments:', error);
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
} else {
|
||||
setExpandedComments(prev => ({ ...prev, [postId]: false }));
|
||||
setExpandedComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,28 +85,28 @@ function Profile() {
|
|||
if (!newComments[postId]?.trim() || !privateKey) return;
|
||||
|
||||
if (newComments[postId].length > 280) {
|
||||
alert('Komentarz nie może być dłuższy niż 280 znaków!');
|
||||
alert("Komentarz nie może być dłuższy niż 280 znaków!");
|
||||
return;
|
||||
}
|
||||
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: true }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: true }));
|
||||
try {
|
||||
await publishPost(newComments[postId], privateKey, postId);
|
||||
setNewComments(prev => ({ ...prev, [postId]: '' }));
|
||||
setNewComments((prev) => ({ ...prev, [postId]: "" }));
|
||||
const fetchedComments = await fetchComments(postId);
|
||||
setComments(prev => ({ ...prev, [postId]: fetchedComments }));
|
||||
setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
|
||||
await loadProfileData();
|
||||
} catch (error) {
|
||||
console.error('Error publishing comment:', error);
|
||||
console.error("Error publishing comment:", error);
|
||||
} finally {
|
||||
setPublishingComments(prev => ({ ...prev, [postId]: false }));
|
||||
setPublishingComments((prev) => ({ ...prev, [postId]: false }));
|
||||
}
|
||||
}
|
||||
|
||||
const filteredPosts = posts.filter(post => {
|
||||
if (activeTab === 'all') return true;
|
||||
if (activeTab === 'mikroblog') return post.tags.includes('mikroblog');
|
||||
if (activeTab === 'main') return !post.tags.includes('mikroblog');
|
||||
const filteredPosts = posts.filter((post) => {
|
||||
if (activeTab === "all") return true;
|
||||
if (activeTab === "mikroblog") return post.tags.includes("mikroblog");
|
||||
if (activeTab === "main") return !post.tags.includes("mikroblog");
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -106,7 +120,7 @@ function Profile() {
|
|||
<div className="profile__header">
|
||||
<div className="profile__avatar">
|
||||
{profile?.picture ? (
|
||||
<img src={profile.picture} alt={profile.name || 'Avatar'} />
|
||||
<img src={profile.picture} alt={profile.name || "Avatar"} />
|
||||
) : (
|
||||
<User size={64} />
|
||||
)}
|
||||
|
@ -117,7 +131,10 @@ function Profile() {
|
|||
<div className="profile__meta">
|
||||
<span>
|
||||
<Calendar size={16} />
|
||||
Dołączył(a): {format(profile?.created_at || Date.now(), 'MMMM yyyy', { locale: pl })}
|
||||
Dołączył(a):{" "}
|
||||
{format(profile?.created_at || Date.now(), "MMMM yyyy", {
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -127,20 +144,23 @@ function Profile() {
|
|||
<div className="card">
|
||||
<div className="tabs">
|
||||
<button
|
||||
className={`tab ${activeTab === 'all' ? 'tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('all')}
|
||||
type="button"
|
||||
className={`tab ${activeTab === "all" ? "tab--active" : ""}`}
|
||||
onClick={() => setActiveTab("all")}
|
||||
>
|
||||
Wszystkie wpisy
|
||||
</button>
|
||||
<button
|
||||
className={`tab ${activeTab === 'mikroblog' ? 'tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('mikroblog')}
|
||||
type="button"
|
||||
className={`tab ${activeTab === "mikroblog" ? "tab--active" : ""}`}
|
||||
onClick={() => setActiveTab("mikroblog")}
|
||||
>
|
||||
Mikroblog
|
||||
</button>
|
||||
<button
|
||||
className={`tab ${activeTab === 'main' ? 'tab--active' : ''}`}
|
||||
onClick={() => setActiveTab('main')}
|
||||
type="button"
|
||||
className={`tab ${activeTab === "main" ? "tab--active" : ""}`}
|
||||
onClick={() => setActiveTab("main")}
|
||||
>
|
||||
Główna
|
||||
</button>
|
||||
|
@ -153,7 +173,8 @@ function Profile() {
|
|||
<div key={post.id} className="post">
|
||||
<div className="post__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, true)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -161,7 +182,8 @@ function Profile() {
|
|||
</button>
|
||||
<span>{post.votes.up - post.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[post.id] ? 'button--disabled' : ''}`}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[post.id] ? "button--disabled" : ""}`}
|
||||
onClick={() => handleVote(post.id, post.author, false)}
|
||||
disabled={!publicKey || votingStates[post.id]}
|
||||
>
|
||||
|
@ -174,11 +196,12 @@ function Profile() {
|
|||
<span>
|
||||
{formatDistanceToNow(post.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleExpandComments(post.id)}
|
||||
className="button button--link"
|
||||
>
|
||||
|
@ -193,45 +216,59 @@ function Profile() {
|
|||
<div className="comments__form">
|
||||
<div className="post-input-wrapper">
|
||||
<textarea
|
||||
value={newComments[post.id] || ''}
|
||||
onChange={(e) => setNewComments(prev => ({
|
||||
value={newComments[post.id] || ""}
|
||||
onChange={(e) =>
|
||||
setNewComments((prev) => ({
|
||||
...prev,
|
||||
[post.id]: e.target.value
|
||||
}))}
|
||||
[post.id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Napisz komentarz... (max 280 znaków)"
|
||||
className="post-input"
|
||||
rows="2"
|
||||
maxLength={280}
|
||||
/>
|
||||
<span className="character-count">
|
||||
{(newComments[post.id] || '').length}/280
|
||||
{(newComments[post.id] || "").length}/280
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handlePublishComment(post.id)}
|
||||
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()}
|
||||
disabled={
|
||||
publishingComments[post.id] ||
|
||||
!newComments[post.id]?.trim()
|
||||
}
|
||||
className="button"
|
||||
>
|
||||
<Reply size={16} />
|
||||
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'}
|
||||
{publishingComments[post.id]
|
||||
? "Wysyłanie..."
|
||||
: "Odpowiedz"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comments[post.id]?.map(comment => (
|
||||
{comments[post.id]?.map((comment) => (
|
||||
<div key={comment.id} className="comment">
|
||||
<div className="comment__votes">
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, true)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, true)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowUp size={16} />
|
||||
</button>
|
||||
<span>{comment.votes.up - comment.votes.down}</span>
|
||||
<button
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`}
|
||||
onClick={() => handleVote(comment.id, comment.author, false)}
|
||||
type="button"
|
||||
className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
|
||||
onClick={() =>
|
||||
handleVote(comment.id, comment.author, false)
|
||||
}
|
||||
disabled={!publicKey || votingStates[comment.id]}
|
||||
>
|
||||
<ArrowDown size={16} />
|
||||
|
@ -247,7 +284,7 @@ function Profile() {
|
|||
<span>
|
||||
{formatDistanceToNow(comment.createdAt * 1000, {
|
||||
addSuffix: true,
|
||||
locale: pl
|
||||
locale: pl,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { create } from 'zustand';
|
||||
import { create } from "zustand";
|
||||
|
||||
export const useStore = create((set) => ({
|
||||
publicKey: null,
|
||||
|
|
|
@ -35,8 +35,8 @@ body {
|
|||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
// Layout
|
||||
|
@ -539,7 +539,7 @@ body {
|
|||
height: 16rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
content: "";
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border: 2px solid $border-color;
|
||||
|
|
|
@ -33,8 +33,6 @@ export async function publishPost(
|
|||
event.tags.push(["e", replyTo]);
|
||||
}
|
||||
|
||||
// event.id = getEventHash(event);
|
||||
// event.sig = await signEvent(event, privateKey);
|
||||
const signedEvent = finalizeEvent(event, privateKey);
|
||||
|
||||
const pubs = pool.publish(RELAYS, signedEvent);
|
||||
|
@ -61,8 +59,6 @@ export async function vote(postId, postAuthor, isUpvote, privateKey) {
|
|||
pubkey: "",
|
||||
};
|
||||
|
||||
// event.id = getEventHash(event);
|
||||
// event.sig = await signEvent(event, privateKey);
|
||||
const signedEvent = finalizeEvent(event, privateKey);
|
||||
|
||||
const pubs = pool.publish(RELAYS, signedEvent);
|
||||
|
@ -360,8 +356,6 @@ export async function updateUserRole(userId, role) {
|
|||
pubkey: "",
|
||||
};
|
||||
|
||||
// event.id = getEventHash(event);
|
||||
// event.sig = await signEvent(event, privateKey);
|
||||
const signedEvent = finalizeEvent(event, privateKey);
|
||||
|
||||
const pubs = pool.publish(RELAYS, signedEvent);
|
||||
|
@ -387,8 +381,6 @@ export async function banUser(userId, isBanned) {
|
|||
pubkey: "",
|
||||
};
|
||||
|
||||
// event.id = getEventHash(event);
|
||||
// event.sig = await signEvent(event, privateKey);
|
||||
const signedEvent = finalizeEvent(event, privateKey);
|
||||
|
||||
const pubs = pool.publish(RELAYS, signedEvent);
|
||||
|
@ -414,8 +406,6 @@ export async function removePost(postId) {
|
|||
pubkey: "",
|
||||
};
|
||||
|
||||
// event.id = getEventHash(event);
|
||||
// event.sig = await signEvent(event, privateKey);
|
||||
const signedEvent = finalizeEvent(event, privateKey);
|
||||
|
||||
const pubs = pool.publish(RELAYS, signedEvent);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue