This commit is contained in:
Sebastian Korotkiewicz 2025-04-10 12:23:10 +02:00
parent 06162e4f66
commit 3d3ee3a087
13 changed files with 332 additions and 238 deletions

View file

@ -26,4 +26,4 @@
"globals": "^16.0.0", "globals": "^16.0.0",
"vite": "^6.2.6" "vite": "^6.2.6"
} }
} }

View file

@ -1,13 +1,13 @@
import React from 'react'; import React from "react";
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Header from './components/Header'; import Header from "./components/Header";
import Home from './pages/Home'; import Home from "./pages/Home";
import Login from './pages/Login'; import Login from "./pages/Login";
import Mikroblog from './pages/Mikroblog'; import Mikroblog from "./pages/Mikroblog";
import Profile from './pages/Profile'; import Profile from "./pages/Profile";
import AdminPanel from './pages/AdminPanel'; import AdminPanel from "./pages/AdminPanel";
// import Settings from './pages/Settings'; // import Settings from './pages/Settings';
import './styles/main.scss'; import "./styles/main.scss";
function App() { function App() {
return ( return (
@ -29,4 +29,4 @@ function App() {
); );
} }
export default App; export default App;

View file

@ -1,7 +1,14 @@
import React from 'react'; import React from "react";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import { LogIn, LogOut, Home, MessageSquare, Settings, Shield } from 'lucide-react'; import {
import { useStore } from '../store/useStore'; LogIn,
LogOut,
Home,
MessageSquare,
Settings,
Shield,
} from "lucide-react";
import { useStore } from "../store/useStore";
function Header() { function Header() {
const { publicKey, profile, logout } = useStore(); const { publicKey, profile, logout } = useStore();
@ -20,11 +27,11 @@ function Header() {
</Link> </Link>
<Link to="/mikroblog">Mikroblog</Link> <Link to="/mikroblog">Mikroblog</Link>
</div> </div>
<div className="header__nav"> <div className="header__nav">
{publicKey ? ( {publicKey ? (
<> <>
{(profile?.role === 'admin' || profile?.role === 'moderator') && ( {(profile?.role === "admin" || profile?.role === "moderator") && (
<Link to="/admin"> <Link to="/admin">
<Shield size={16} /> <Shield size={16} />
Panel Panel
@ -33,7 +40,7 @@ function Header() {
<Link to="/settings"> <Link to="/settings">
<Settings size={20} /> <Settings size={20} />
</Link> </Link>
<button onClick={logout} className="button"> <button type="button" onClick={logout} className="button">
<LogOut size={20} /> <LogOut size={20} />
</button> </button>
</> </>
@ -49,4 +56,4 @@ function Header() {
); );
} }
export default Header; export default Header;

View file

@ -1,9 +1,9 @@
import { StrictMode } from 'react'; import { StrictMode } from "react";
import { createRoot } from 'react-dom/client'; import { createRoot } from "react-dom/client";
import App from './App'; import App from "./App";
createRoot(document.getElementById('root')).render( createRoot(document.getElementById("root")).render(
<StrictMode> <StrictMode>
<App /> <App />
</StrictMode> </StrictMode>,
); );

View file

@ -1,23 +1,40 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import { Shield, User, MessageSquare, Ban, CheckCircle, XCircle } from 'lucide-react'; import {
import { useStore } from '../store/useStore'; Shield,
import { fetchAllPosts, fetchAllUsers, updateUserRole, banUser, removePost } from '../utils/nostr'; User,
import { formatDistanceToNow } from 'date-fns'; MessageSquare,
import { pl } from 'date-fns/locale'; 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() { function AdminPanel() {
const navigate = useNavigate(); const navigate = useNavigate();
const { publicKey, profile } = useStore(); const { publicKey, profile } = useStore();
const [activeTab, setActiveTab] = useState('users'); const [activeTab, setActiveTab] = useState("users");
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [processingAction, setProcessingAction] = useState({}); const [processingAction, setProcessingAction] = useState({});
useEffect(() => { useEffect(() => {
if (!publicKey || !profile || (profile.role !== 'admin' && profile.role !== 'moderator')) { if (
navigate('/'); !publicKey ||
!profile ||
(profile.role !== "admin" && profile.role !== "moderator")
) {
navigate("/");
return; return;
} }
@ -29,12 +46,12 @@ function AdminPanel() {
try { try {
const [fetchedUsers, fetchedPosts] = await Promise.all([ const [fetchedUsers, fetchedPosts] = await Promise.all([
fetchAllUsers(), fetchAllUsers(),
fetchAllPosts() fetchAllPosts(),
]); ]);
setUsers(fetchedUsers); setUsers(fetchedUsers);
setPosts(fetchedPosts); setPosts(fetchedPosts);
} catch (error) { } catch (error) {
console.error('Error loading admin data:', error); console.error("Error loading admin data:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -43,42 +60,42 @@ function AdminPanel() {
async function handleUpdateRole(userId, newRole) { async function handleUpdateRole(userId, newRole) {
if (processingAction[userId]) return; if (processingAction[userId]) return;
setProcessingAction(prev => ({ ...prev, [userId]: true })); setProcessingAction((prev) => ({ ...prev, [userId]: true }));
try { try {
await updateUserRole(userId, newRole); await updateUserRole(userId, newRole);
await loadData(); await loadData();
} catch (error) { } catch (error) {
console.error('Error updating user role:', error); console.error("Error updating user role:", error);
} finally { } finally {
setProcessingAction(prev => ({ ...prev, [userId]: false })); setProcessingAction((prev) => ({ ...prev, [userId]: false }));
} }
} }
async function handleBanUser(userId, isBanned) { async function handleBanUser(userId, isBanned) {
if (processingAction[userId]) return; if (processingAction[userId]) return;
setProcessingAction(prev => ({ ...prev, [userId]: true })); setProcessingAction((prev) => ({ ...prev, [userId]: true }));
try { try {
await banUser(userId, isBanned); await banUser(userId, isBanned);
await loadData(); await loadData();
} catch (error) { } catch (error) {
console.error('Error updating user ban status:', error); console.error("Error updating user ban status:", error);
} finally { } finally {
setProcessingAction(prev => ({ ...prev, [userId]: false })); setProcessingAction((prev) => ({ ...prev, [userId]: false }));
} }
} }
async function handleRemovePost(postId) { async function handleRemovePost(postId) {
if (processingAction[postId]) return; if (processingAction[postId]) return;
setProcessingAction(prev => ({ ...prev, [postId]: true })); setProcessingAction((prev) => ({ ...prev, [postId]: true }));
try { try {
await removePost(postId); await removePost(postId);
await loadData(); await loadData();
} catch (error) { } catch (error) {
console.error('Error removing post:', error); console.error("Error removing post:", error);
} finally { } finally {
setProcessingAction(prev => ({ ...prev, [postId]: false })); setProcessingAction((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -92,28 +109,30 @@ function AdminPanel() {
<div className="admin-panel__header"> <div className="admin-panel__header">
<h1> <h1>
<Shield size={24} /> <Shield size={24} />
Panel {profile?.role === 'admin' ? 'Administratora' : 'Moderatora'} Panel {profile?.role === "admin" ? "Administratora" : "Moderatora"}
</h1> </h1>
</div> </div>
<div className="tabs"> <div className="tabs">
<button <button
className={`tab ${activeTab === 'users' ? 'tab--active' : ''}`} type="button"
onClick={() => setActiveTab('users')} className={`tab ${activeTab === "users" ? "tab--active" : ""}`}
onClick={() => setActiveTab("users")}
> >
<User size={16} /> <User size={16} />
Użytkownicy Użytkownicy
</button> </button>
<button <button
className={`tab ${activeTab === 'posts' ? 'tab--active' : ''}`} type="button"
onClick={() => setActiveTab('posts')} className={`tab ${activeTab === "posts" ? "tab--active" : ""}`}
onClick={() => setActiveTab("posts")}
> >
<MessageSquare size={16} /> <MessageSquare size={16} />
Wpisy Wpisy
</button> </button>
</div> </div>
{activeTab === 'users' ? ( {activeTab === "users" ? (
<div className="admin-panel__users"> <div className="admin-panel__users">
<table className="table"> <table className="table">
<thead> <thead>
@ -125,8 +144,11 @@ function AdminPanel() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map(user => ( {users.map((user) => (
<tr key={user.id} className={user.banned ? 'table__row--banned' : ''}> <tr
key={user.id}
className={user.banned ? "table__row--banned" : ""}
>
<td> <td>
<div className="table__user"> <div className="table__user">
{user.picture ? ( {user.picture ? (
@ -140,7 +162,7 @@ function AdminPanel() {
</div> </div>
</div> </div>
</td> </td>
<td>{user.role || 'user'}</td> <td>{user.role || "user"}</td>
<td> <td>
{user.banned ? ( {user.banned ? (
<span className="badge badge--danger"> <span className="badge badge--danger">
@ -156,10 +178,12 @@ function AdminPanel() {
</td> </td>
<td> <td>
<div className="table__actions"> <div className="table__actions">
{profile?.role === 'admin' && ( {profile?.role === "admin" && (
<select <select
value={user.role || 'user'} value={user.role || "user"}
onChange={(e) => handleUpdateRole(user.id, e.target.value)} onChange={(e) =>
handleUpdateRole(user.id, e.target.value)
}
disabled={processingAction[user.id]} disabled={processingAction[user.id]}
> >
<option value="user">Użytkownik</option> <option value="user">Użytkownik</option>
@ -168,7 +192,8 @@ function AdminPanel() {
</select> </select>
)} )}
<button <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)} onClick={() => handleBanUser(user.id, !user.banned)}
disabled={processingAction[user.id]} disabled={processingAction[user.id]}
> >
@ -203,17 +228,22 @@ function AdminPanel() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{posts.map(post => ( {posts.map((post) => (
<tr key={post.id}> <tr key={post.id}>
<td> <td>
<div className="table__user"> <div className="table__user">
{post.author.picture ? ( {post.author.picture ? (
<img src={post.author.picture} alt={post.author.name} /> <img
src={post.author.picture}
alt={post.author.name}
/>
) : ( ) : (
<User size={24} /> <User size={24} />
)} )}
<div> <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> <small>{post.author.id}</small>
</div> </div>
</div> </div>
@ -222,24 +252,21 @@ function AdminPanel() {
<div className="table__content"> <div className="table__content">
<p>{post.content}</p> <p>{post.content}</p>
<div className="table__meta"> <div className="table__meta">
<span> <span>{post.votes.up - post.votes.down} punktów</span>
{post.votes.up - post.votes.down} punktów
</span>
<span></span> <span></span>
<span> <span>{post.comments} komentarzy</span>
{post.comments} komentarzy
</span>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
{formatDistanceToNow(post.createdAt * 1000, { {formatDistanceToNow(post.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</td> </td>
<td> <td>
<button <button
type="button"
className="button button--danger" className="button button--danger"
onClick={() => handleRemovePost(post.id)} onClick={() => handleRemovePost(post.id)}
disabled={processingAction[post.id]} disabled={processingAction[post.id]}
@ -259,4 +286,4 @@ function AdminPanel() {
); );
} }
export default AdminPanel; export default AdminPanel;

View file

@ -1,14 +1,14 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from 'lucide-react'; import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from "lucide-react";
import { useStore } from '../store/useStore'; import { useStore } from "../store/useStore";
import { fetchPosts, publishPost, vote, fetchComments } from '../utils/nostr'; import { fetchPosts, publishPost, vote, fetchComments } from "../utils/nostr";
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from "date-fns";
import { pl } from 'date-fns/locale'; import { pl } from "date-fns/locale";
function Home() { function Home() {
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [newPost, setNewPost] = useState(''); const [newPost, setNewPost] = useState("");
const [publishing, setPublishing] = useState(false); const [publishing, setPublishing] = useState(false);
const [votingStates, setVotingStates] = useState({}); const [votingStates, setVotingStates] = useState({});
const [expandedComments, setExpandedComments] = useState({}); const [expandedComments, setExpandedComments] = useState({});
@ -26,7 +26,7 @@ function Home() {
const fetchedPosts = await fetchPosts(); const fetchedPosts = await fetchPosts();
setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt)); setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt));
} catch (error) { } catch (error) {
console.error('Error loading posts:', error); console.error("Error loading posts:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -39,10 +39,10 @@ function Home() {
setPublishing(true); setPublishing(true);
try { try {
await publishPost(newPost, privateKey); await publishPost(newPost, privateKey);
setNewPost(''); setNewPost("");
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error publishing post:', error); console.error("Error publishing post:", error);
} finally { } finally {
setPublishing(false); setPublishing(false);
} }
@ -51,14 +51,14 @@ function Home() {
async function handleVote(postId, postAuthor, isUpvote) { async function handleVote(postId, postAuthor, isUpvote) {
if (!privateKey || votingStates[postId]) return; if (!privateKey || votingStates[postId]) return;
setVotingStates(prev => ({ ...prev, [postId]: true })); setVotingStates((prev) => ({ ...prev, [postId]: true }));
try { try {
await vote(postId, postAuthor, isUpvote, privateKey); await vote(postId, postAuthor, isUpvote, privateKey);
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error voting:', error); console.error("Error voting:", error);
} finally { } finally {
setVotingStates(prev => ({ ...prev, [postId]: false })); setVotingStates((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -66,30 +66,30 @@ function Home() {
if (!expandedComments[postId]) { if (!expandedComments[postId]) {
try { try {
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
setExpandedComments(prev => ({ ...prev, [postId]: true })); setExpandedComments((prev) => ({ ...prev, [postId]: true }));
} catch (error) { } catch (error) {
console.error('Error fetching comments:', error); console.error("Error fetching comments:", error);
} }
} else { } else {
setExpandedComments(prev => ({ ...prev, [postId]: false })); setExpandedComments((prev) => ({ ...prev, [postId]: false }));
} }
} }
async function handlePublishComment(postId) { async function handlePublishComment(postId) {
if (!newComments[postId]?.trim() || !privateKey) return; if (!newComments[postId]?.trim() || !privateKey) return;
setPublishingComments(prev => ({ ...prev, [postId]: true })); setPublishingComments((prev) => ({ ...prev, [postId]: true }));
try { try {
await publishPost(newComments[postId], privateKey, postId); await publishPost(newComments[postId], privateKey, postId);
setNewComments(prev => ({ ...prev, [postId]: '' })); setNewComments((prev) => ({ ...prev, [postId]: "" }));
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error publishing comment:', error); console.error("Error publishing comment:", error);
} finally { } finally {
setPublishingComments(prev => ({ ...prev, [postId]: false })); setPublishingComments((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -115,7 +115,7 @@ function Home() {
className="button" className="button"
> >
<Send size={20} /> <Send size={20} />
{publishing ? 'Publikowanie...' : 'Opublikuj'} {publishing ? "Publikowanie..." : "Opublikuj"}
</button> </button>
</form> </form>
</div> </div>
@ -130,7 +130,8 @@ function Home() {
<div key={post.id} className="post"> <div key={post.id} className="post">
<div className="post__votes"> <div className="post__votes">
<button <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)} onClick={() => handleVote(post.id, post.author, true)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -138,7 +139,8 @@ function Home() {
</button> </button>
<span>{post.votes.up - post.votes.down}</span> <span>{post.votes.up - post.votes.down}</span>
<button <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)} onClick={() => handleVote(post.id, post.author, false)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -153,11 +155,12 @@ function Home() {
<span> <span>
{formatDistanceToNow(post.createdAt * 1000, { {formatDistanceToNow(post.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
<span></span> <span></span>
<button <button
type="button"
onClick={() => handleExpandComments(post.id)} onClick={() => handleExpandComments(post.id)}
className="button button--link" className="button button--link"
> >
@ -171,40 +174,54 @@ function Home() {
{publicKey && ( {publicKey && (
<div className="comments__form"> <div className="comments__form">
<textarea <textarea
value={newComments[post.id] || ''} value={newComments[post.id] || ""}
onChange={(e) => setNewComments(prev => ({ onChange={(e) =>
...prev, setNewComments((prev) => ({
[post.id]: e.target.value ...prev,
}))} [post.id]: e.target.value,
}))
}
placeholder="Napisz komentarz..." placeholder="Napisz komentarz..."
className="post-input" className="post-input"
rows="2" rows="2"
/> />
<button <button
type="button"
onClick={() => handlePublishComment(post.id)} onClick={() => handlePublishComment(post.id)}
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()} disabled={
publishingComments[post.id] ||
!newComments[post.id]?.trim()
}
className="button" className="button"
> >
<Reply size={16} /> <Reply size={16} />
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'} {publishingComments[post.id]
? "Wysyłanie..."
: "Odpowiedz"}
</button> </button>
</div> </div>
)} )}
{comments[post.id]?.map(comment => ( {comments[post.id]?.map((comment) => (
<div key={comment.id} className="comment"> <div key={comment.id} className="comment">
<div className="comment__votes"> <div className="comment__votes">
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, true)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, true)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowUp size={16} /> <ArrowUp size={16} />
</button> </button>
<span>{comment.votes.up - comment.votes.down}</span> <span>{comment.votes.up - comment.votes.down}</span>
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, false)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, false)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowDown size={16} /> <ArrowDown size={16} />
@ -218,7 +235,7 @@ function Home() {
<span> <span>
{formatDistanceToNow(comment.createdAt * 1000, { {formatDistanceToNow(comment.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
</div> </div>
@ -236,4 +253,4 @@ function Home() {
); );
} }
export default Home; export default Home;

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import { generateSecretKey, getPublicKey } from 'nostr-tools'; import { generateSecretKey, getPublicKey } from "nostr-tools";
import { useStore } from '../store/useStore'; import { useStore } from "../store/useStore";
function Login() { function Login() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -14,9 +14,9 @@ function Login() {
const privateKey = generateSecretKey(); const privateKey = generateSecretKey();
const publicKey = getPublicKey(privateKey); const publicKey = getPublicKey(privateKey);
setKeys(publicKey, privateKey); setKeys(publicKey, privateKey);
navigate('/'); navigate("/");
} catch (error) { } catch (error) {
console.error('Error generating keys:', error); console.error("Error generating keys:", error);
} }
setLoading(false); setLoading(false);
}; };
@ -26,11 +26,12 @@ function Login() {
<h2>Zaloguj się</h2> <h2>Zaloguj się</h2>
<div> <div>
<button <button
type="button"
onClick={handleGenerateKeys} onClick={handleGenerateKeys}
disabled={loading} disabled={loading}
className="button button--full" className="button button--full"
> >
{loading ? 'Generowanie...' : 'Wygeneruj nowy klucz'} {loading ? "Generowanie..." : "Wygeneruj nowy klucz"}
</button> </button>
<p className="text-light"> <p className="text-light">
Twój klucz prywatny zostanie bezpiecznie zapisany w przeglądarce Twój klucz prywatny zostanie bezpiecznie zapisany w przeglądarce
@ -40,4 +41,4 @@ function Login() {
); );
} }
export default Login; export default Login;

View file

@ -1,14 +1,14 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from 'lucide-react'; import { ArrowUp, ArrowDown, MessageSquare, Send, Reply } from "lucide-react";
import { useStore } from '../store/useStore'; import { useStore } from "../store/useStore";
import { fetchPosts, publishPost, vote, fetchComments } from '../utils/nostr'; import { fetchPosts, publishPost, vote, fetchComments } from "../utils/nostr";
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from "date-fns";
import { pl } from 'date-fns/locale'; import { pl } from "date-fns/locale";
function Mikroblog() { function Mikroblog() {
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [newPost, setNewPost] = useState(''); const [newPost, setNewPost] = useState("");
const [publishing, setPublishing] = useState(false); const [publishing, setPublishing] = useState(false);
const [votingStates, setVotingStates] = useState({}); const [votingStates, setVotingStates] = useState({});
const [expandedComments, setExpandedComments] = useState({}); const [expandedComments, setExpandedComments] = useState({});
@ -23,10 +23,10 @@ function Mikroblog() {
async function loadPosts() { async function loadPosts() {
try { try {
const fetchedPosts = await fetchPosts('mikroblog'); const fetchedPosts = await fetchPosts("mikroblog");
setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt)); setPosts(fetchedPosts.sort((a, b) => b.createdAt - a.createdAt));
} catch (error) { } catch (error) {
console.error('Error loading posts:', error); console.error("Error loading posts:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -37,17 +37,17 @@ function Mikroblog() {
if (!newPost.trim() || !privateKey) return; if (!newPost.trim() || !privateKey) return;
if (newPost.length > 280) { 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; return;
} }
setPublishing(true); setPublishing(true);
try { try {
await publishPost(newPost, privateKey, null, 'mikroblog'); await publishPost(newPost, privateKey, null, "mikroblog");
setNewPost(''); setNewPost("");
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error publishing post:', error); console.error("Error publishing post:", error);
} finally { } finally {
setPublishing(false); setPublishing(false);
} }
@ -56,14 +56,14 @@ function Mikroblog() {
async function handleVote(postId, postAuthor, isUpvote) { async function handleVote(postId, postAuthor, isUpvote) {
if (!privateKey || votingStates[postId]) return; if (!privateKey || votingStates[postId]) return;
setVotingStates(prev => ({ ...prev, [postId]: true })); setVotingStates((prev) => ({ ...prev, [postId]: true }));
try { try {
await vote(postId, postAuthor, isUpvote, privateKey); await vote(postId, postAuthor, isUpvote, privateKey);
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error voting:', error); console.error("Error voting:", error);
} finally { } finally {
setVotingStates(prev => ({ ...prev, [postId]: false })); setVotingStates((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -71,13 +71,13 @@ function Mikroblog() {
if (!expandedComments[postId]) { if (!expandedComments[postId]) {
try { try {
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
setExpandedComments(prev => ({ ...prev, [postId]: true })); setExpandedComments((prev) => ({ ...prev, [postId]: true }));
} catch (error) { } catch (error) {
console.error('Error fetching comments:', error); console.error("Error fetching comments:", error);
} }
} else { } 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]?.trim() || !privateKey) return;
if (newComments[postId].length > 280) { 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; return;
} }
setPublishingComments(prev => ({ ...prev, [postId]: true })); setPublishingComments((prev) => ({ ...prev, [postId]: true }));
try { try {
await publishPost(newComments[postId], privateKey, postId, 'mikroblog'); await publishPost(newComments[postId], privateKey, postId, "mikroblog");
setNewComments(prev => ({ ...prev, [postId]: '' })); setNewComments((prev) => ({ ...prev, [postId]: "" }));
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
await loadPosts(); await loadPosts();
} catch (error) { } catch (error) {
console.error('Error publishing comment:', error); console.error("Error publishing comment:", error);
} finally { } finally {
setPublishingComments(prev => ({ ...prev, [postId]: false })); setPublishingComments((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -121,9 +121,7 @@ function Mikroblog() {
rows="3" rows="3"
maxLength={280} maxLength={280}
/> />
<span className="character-count"> <span className="character-count">{newPost.length}/280</span>
{newPost.length}/280
</span>
</div> </div>
<button <button
type="submit" type="submit"
@ -131,7 +129,7 @@ function Mikroblog() {
className="button" className="button"
> >
<Send size={20} /> <Send size={20} />
{publishing ? 'Publikowanie...' : 'Opublikuj'} {publishing ? "Publikowanie..." : "Opublikuj"}
</button> </button>
</form> </form>
</div> </div>
@ -146,7 +144,8 @@ function Mikroblog() {
<div key={post.id} className="post"> <div key={post.id} className="post">
<div className="post__votes"> <div className="post__votes">
<button <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)} onClick={() => handleVote(post.id, post.author, true)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -154,7 +153,8 @@ function Mikroblog() {
</button> </button>
<span>{post.votes.up - post.votes.down}</span> <span>{post.votes.up - post.votes.down}</span>
<button <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)} onClick={() => handleVote(post.id, post.author, false)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -169,11 +169,12 @@ function Mikroblog() {
<span> <span>
{formatDistanceToNow(post.createdAt * 1000, { {formatDistanceToNow(post.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
<span></span> <span></span>
<button <button
type="button"
onClick={() => handleExpandComments(post.id)} onClick={() => handleExpandComments(post.id)}
className="button button--link" className="button button--link"
> >
@ -188,45 +189,59 @@ function Mikroblog() {
<div className="comments__form"> <div className="comments__form">
<div className="post-input-wrapper"> <div className="post-input-wrapper">
<textarea <textarea
value={newComments[post.id] || ''} value={newComments[post.id] || ""}
onChange={(e) => setNewComments(prev => ({ onChange={(e) =>
...prev, setNewComments((prev) => ({
[post.id]: e.target.value ...prev,
}))} [post.id]: e.target.value,
}))
}
placeholder="Napisz komentarz... (max 280 znaków)" placeholder="Napisz komentarz... (max 280 znaków)"
className="post-input" className="post-input"
rows="2" rows="2"
maxLength={280} maxLength={280}
/> />
<span className="character-count"> <span className="character-count">
{(newComments[post.id] || '').length}/280 {(newComments[post.id] || "").length}/280
</span> </span>
</div> </div>
<button <button
type="button"
onClick={() => handlePublishComment(post.id)} onClick={() => handlePublishComment(post.id)}
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()} disabled={
publishingComments[post.id] ||
!newComments[post.id]?.trim()
}
className="button" className="button"
> >
<Reply size={16} /> <Reply size={16} />
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'} {publishingComments[post.id]
? "Wysyłanie..."
: "Odpowiedz"}
</button> </button>
</div> </div>
)} )}
{comments[post.id]?.map(comment => ( {comments[post.id]?.map((comment) => (
<div key={comment.id} className="comment"> <div key={comment.id} className="comment">
<div className="comment__votes"> <div className="comment__votes">
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, true)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, true)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowUp size={16} /> <ArrowUp size={16} />
</button> </button>
<span>{comment.votes.up - comment.votes.down}</span> <span>{comment.votes.up - comment.votes.down}</span>
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, false)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, false)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowDown size={16} /> <ArrowDown size={16} />
@ -240,7 +255,7 @@ function Mikroblog() {
<span> <span>
{formatDistanceToNow(comment.createdAt * 1000, { {formatDistanceToNow(comment.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
</div> </div>
@ -258,4 +273,4 @@ function Mikroblog() {
); );
} }
export default Mikroblog; export default Mikroblog;

View file

@ -1,10 +1,24 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { useParams, Link } from 'react-router-dom'; import { useParams, Link } from "react-router-dom";
import { ArrowUp, ArrowDown, MessageSquare, Send, Reply, User, Calendar } from 'lucide-react'; import {
import { useStore } from '../store/useStore'; ArrowUp,
import { fetchUserPosts, fetchUserProfile, publishPost, vote, fetchComments } from '../utils/nostr'; ArrowDown,
import { formatDistanceToNow, format } from 'date-fns'; MessageSquare,
import { pl } from 'date-fns/locale'; 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() { function Profile() {
const { pubkey } = useParams(); const { pubkey } = useParams();
@ -17,7 +31,7 @@ function Profile() {
const [newComments, setNewComments] = useState({}); const [newComments, setNewComments] = useState({});
const [publishingComments, setPublishingComments] = useState({}); const [publishingComments, setPublishingComments] = useState({});
const { publicKey, privateKey } = useStore(); const { publicKey, privateKey } = useStore();
const [activeTab, setActiveTab] = useState('all'); const [activeTab, setActiveTab] = useState("all");
useEffect(() => { useEffect(() => {
loadProfileData(); loadProfileData();
@ -28,12 +42,12 @@ function Profile() {
try { try {
const [userProfile, userPosts] = await Promise.all([ const [userProfile, userPosts] = await Promise.all([
fetchUserProfile(pubkey), fetchUserProfile(pubkey),
fetchUserPosts(pubkey) fetchUserPosts(pubkey),
]); ]);
setProfile(userProfile); setProfile(userProfile);
setPosts(userPosts.sort((a, b) => b.createdAt - a.createdAt)); setPosts(userPosts.sort((a, b) => b.createdAt - a.createdAt));
} catch (error) { } catch (error) {
console.error('Error loading profile data:', error); console.error("Error loading profile data:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -42,14 +56,14 @@ function Profile() {
async function handleVote(postId, postAuthor, isUpvote) { async function handleVote(postId, postAuthor, isUpvote) {
if (!privateKey || votingStates[postId]) return; if (!privateKey || votingStates[postId]) return;
setVotingStates(prev => ({ ...prev, [postId]: true })); setVotingStates((prev) => ({ ...prev, [postId]: true }));
try { try {
await vote(postId, postAuthor, isUpvote, privateKey); await vote(postId, postAuthor, isUpvote, privateKey);
await loadProfileData(); await loadProfileData();
} catch (error) { } catch (error) {
console.error('Error voting:', error); console.error("Error voting:", error);
} finally { } finally {
setVotingStates(prev => ({ ...prev, [postId]: false })); setVotingStates((prev) => ({ ...prev, [postId]: false }));
} }
} }
@ -57,13 +71,13 @@ function Profile() {
if (!expandedComments[postId]) { if (!expandedComments[postId]) {
try { try {
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
setExpandedComments(prev => ({ ...prev, [postId]: true })); setExpandedComments((prev) => ({ ...prev, [postId]: true }));
} catch (error) { } catch (error) {
console.error('Error fetching comments:', error); console.error("Error fetching comments:", error);
} }
} else { } 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]?.trim() || !privateKey) return;
if (newComments[postId].length > 280) { 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; return;
} }
setPublishingComments(prev => ({ ...prev, [postId]: true })); setPublishingComments((prev) => ({ ...prev, [postId]: true }));
try { try {
await publishPost(newComments[postId], privateKey, postId); await publishPost(newComments[postId], privateKey, postId);
setNewComments(prev => ({ ...prev, [postId]: '' })); setNewComments((prev) => ({ ...prev, [postId]: "" }));
const fetchedComments = await fetchComments(postId); const fetchedComments = await fetchComments(postId);
setComments(prev => ({ ...prev, [postId]: fetchedComments })); setComments((prev) => ({ ...prev, [postId]: fetchedComments }));
await loadProfileData(); await loadProfileData();
} catch (error) { } catch (error) {
console.error('Error publishing comment:', error); console.error("Error publishing comment:", error);
} finally { } finally {
setPublishingComments(prev => ({ ...prev, [postId]: false })); setPublishingComments((prev) => ({ ...prev, [postId]: false }));
} }
} }
const filteredPosts = posts.filter(post => { const filteredPosts = posts.filter((post) => {
if (activeTab === 'all') return true; if (activeTab === "all") return true;
if (activeTab === 'mikroblog') return post.tags.includes('mikroblog'); if (activeTab === "mikroblog") return post.tags.includes("mikroblog");
if (activeTab === 'main') return !post.tags.includes('mikroblog'); if (activeTab === "main") return !post.tags.includes("mikroblog");
return true; return true;
}); });
@ -106,7 +120,7 @@ function Profile() {
<div className="profile__header"> <div className="profile__header">
<div className="profile__avatar"> <div className="profile__avatar">
{profile?.picture ? ( {profile?.picture ? (
<img src={profile.picture} alt={profile.name || 'Avatar'} /> <img src={profile.picture} alt={profile.name || "Avatar"} />
) : ( ) : (
<User size={64} /> <User size={64} />
)} )}
@ -117,7 +131,10 @@ function Profile() {
<div className="profile__meta"> <div className="profile__meta">
<span> <span>
<Calendar size={16} /> <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> </span>
</div> </div>
</div> </div>
@ -127,20 +144,23 @@ function Profile() {
<div className="card"> <div className="card">
<div className="tabs"> <div className="tabs">
<button <button
className={`tab ${activeTab === 'all' ? 'tab--active' : ''}`} type="button"
onClick={() => setActiveTab('all')} className={`tab ${activeTab === "all" ? "tab--active" : ""}`}
onClick={() => setActiveTab("all")}
> >
Wszystkie wpisy Wszystkie wpisy
</button> </button>
<button <button
className={`tab ${activeTab === 'mikroblog' ? 'tab--active' : ''}`} type="button"
onClick={() => setActiveTab('mikroblog')} className={`tab ${activeTab === "mikroblog" ? "tab--active" : ""}`}
onClick={() => setActiveTab("mikroblog")}
> >
Mikroblog Mikroblog
</button> </button>
<button <button
className={`tab ${activeTab === 'main' ? 'tab--active' : ''}`} type="button"
onClick={() => setActiveTab('main')} className={`tab ${activeTab === "main" ? "tab--active" : ""}`}
onClick={() => setActiveTab("main")}
> >
Główna Główna
</button> </button>
@ -153,7 +173,8 @@ function Profile() {
<div key={post.id} className="post"> <div key={post.id} className="post">
<div className="post__votes"> <div className="post__votes">
<button <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)} onClick={() => handleVote(post.id, post.author, true)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -161,7 +182,8 @@ function Profile() {
</button> </button>
<span>{post.votes.up - post.votes.down}</span> <span>{post.votes.up - post.votes.down}</span>
<button <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)} onClick={() => handleVote(post.id, post.author, false)}
disabled={!publicKey || votingStates[post.id]} disabled={!publicKey || votingStates[post.id]}
> >
@ -174,11 +196,12 @@ function Profile() {
<span> <span>
{formatDistanceToNow(post.createdAt * 1000, { {formatDistanceToNow(post.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
<span></span> <span></span>
<button <button
type="button"
onClick={() => handleExpandComments(post.id)} onClick={() => handleExpandComments(post.id)}
className="button button--link" className="button button--link"
> >
@ -193,45 +216,59 @@ function Profile() {
<div className="comments__form"> <div className="comments__form">
<div className="post-input-wrapper"> <div className="post-input-wrapper">
<textarea <textarea
value={newComments[post.id] || ''} value={newComments[post.id] || ""}
onChange={(e) => setNewComments(prev => ({ onChange={(e) =>
...prev, setNewComments((prev) => ({
[post.id]: e.target.value ...prev,
}))} [post.id]: e.target.value,
}))
}
placeholder="Napisz komentarz... (max 280 znaków)" placeholder="Napisz komentarz... (max 280 znaków)"
className="post-input" className="post-input"
rows="2" rows="2"
maxLength={280} maxLength={280}
/> />
<span className="character-count"> <span className="character-count">
{(newComments[post.id] || '').length}/280 {(newComments[post.id] || "").length}/280
</span> </span>
</div> </div>
<button <button
type="button"
onClick={() => handlePublishComment(post.id)} onClick={() => handlePublishComment(post.id)}
disabled={publishingComments[post.id] || !newComments[post.id]?.trim()} disabled={
publishingComments[post.id] ||
!newComments[post.id]?.trim()
}
className="button" className="button"
> >
<Reply size={16} /> <Reply size={16} />
{publishingComments[post.id] ? 'Wysyłanie...' : 'Odpowiedz'} {publishingComments[post.id]
? "Wysyłanie..."
: "Odpowiedz"}
</button> </button>
</div> </div>
)} )}
{comments[post.id]?.map(comment => ( {comments[post.id]?.map((comment) => (
<div key={comment.id} className="comment"> <div key={comment.id} className="comment">
<div className="comment__votes"> <div className="comment__votes">
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, true)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, true)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowUp size={16} /> <ArrowUp size={16} />
</button> </button>
<span>{comment.votes.up - comment.votes.down}</span> <span>{comment.votes.up - comment.votes.down}</span>
<button <button
className={`button ${!publicKey || votingStates[comment.id] ? 'button--disabled' : ''}`} type="button"
onClick={() => handleVote(comment.id, comment.author, false)} className={`button ${!publicKey || votingStates[comment.id] ? "button--disabled" : ""}`}
onClick={() =>
handleVote(comment.id, comment.author, false)
}
disabled={!publicKey || votingStates[comment.id]} disabled={!publicKey || votingStates[comment.id]}
> >
<ArrowDown size={16} /> <ArrowDown size={16} />
@ -247,7 +284,7 @@ function Profile() {
<span> <span>
{formatDistanceToNow(comment.createdAt * 1000, { {formatDistanceToNow(comment.createdAt * 1000, {
addSuffix: true, addSuffix: true,
locale: pl locale: pl,
})} })}
</span> </span>
</div> </div>
@ -265,4 +302,4 @@ function Profile() {
); );
} }
export default Profile; export default Profile;

View file

@ -1,4 +1,4 @@
import { create } from 'zustand'; import { create } from "zustand";
export const useStore = create((set) => ({ export const useStore = create((set) => ({
publicKey: null, publicKey: null,
@ -7,4 +7,4 @@ export const useStore = create((set) => ({
setKeys: (publicKey, privateKey) => set({ publicKey, privateKey }), setKeys: (publicKey, privateKey) => set({ publicKey, privateKey }),
setProfile: (profile) => set({ profile }), setProfile: (profile) => set({ profile }),
logout: () => set({ publicKey: null, privateKey: null, profile: null }), logout: () => set({ publicKey: null, privateKey: null, profile: null }),
})); }));

View file

@ -35,8 +35,8 @@ body {
background-color: $bg-color; background-color: $bg-color;
color: $text-color; color: $text-color;
min-height: 100vh; min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
} }
// Layout // Layout
@ -539,7 +539,7 @@ body {
height: 16rem; height: 16rem;
&::after { &::after {
content: ''; content: "";
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
border: 2px solid $border-color; border: 2px solid $border-color;
@ -558,4 +558,4 @@ body {
// Text utilities // Text utilities
.text-light { .text-light {
color: $text-light; color: $text-light;
} }

View file

@ -33,8 +33,6 @@ export async function publishPost(
event.tags.push(["e", replyTo]); event.tags.push(["e", replyTo]);
} }
// event.id = getEventHash(event);
// event.sig = await signEvent(event, privateKey);
const signedEvent = finalizeEvent(event, privateKey); const signedEvent = finalizeEvent(event, privateKey);
const pubs = pool.publish(RELAYS, signedEvent); const pubs = pool.publish(RELAYS, signedEvent);
@ -61,8 +59,6 @@ export async function vote(postId, postAuthor, isUpvote, privateKey) {
pubkey: "", pubkey: "",
}; };
// event.id = getEventHash(event);
// event.sig = await signEvent(event, privateKey);
const signedEvent = finalizeEvent(event, privateKey); const signedEvent = finalizeEvent(event, privateKey);
const pubs = pool.publish(RELAYS, signedEvent); const pubs = pool.publish(RELAYS, signedEvent);
@ -360,8 +356,6 @@ export async function updateUserRole(userId, role) {
pubkey: "", pubkey: "",
}; };
// event.id = getEventHash(event);
// event.sig = await signEvent(event, privateKey);
const signedEvent = finalizeEvent(event, privateKey); const signedEvent = finalizeEvent(event, privateKey);
const pubs = pool.publish(RELAYS, signedEvent); const pubs = pool.publish(RELAYS, signedEvent);
@ -387,8 +381,6 @@ export async function banUser(userId, isBanned) {
pubkey: "", pubkey: "",
}; };
// event.id = getEventHash(event);
// event.sig = await signEvent(event, privateKey);
const signedEvent = finalizeEvent(event, privateKey); const signedEvent = finalizeEvent(event, privateKey);
const pubs = pool.publish(RELAYS, signedEvent); const pubs = pool.publish(RELAYS, signedEvent);
@ -414,8 +406,6 @@ export async function removePost(postId) {
pubkey: "", pubkey: "",
}; };
// event.id = getEventHash(event);
// event.sig = await signEvent(event, privateKey);
const signedEvent = finalizeEvent(event, privateKey); const signedEvent = finalizeEvent(event, privateKey);
const pubs = pool.publish(RELAYS, signedEvent); const pubs = pool.publish(RELAYS, signedEvent);

View file

@ -1,5 +1,5 @@
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
import react from '@vitejs/plugin-react'; import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({