Apply UI updates: tags gap, notifications, card click, breadcrumb, move share input
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -5,42 +5,37 @@ import useAppStore from '../store/useAppStore'
|
||||
const CreateUser: React.FC = () => {
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [bio, setBio] = useState('')
|
||||
const [specialties, setSpecialties] = useState('')
|
||||
const createUser = useAppStore((s) => s.createUser)
|
||||
const addNotification = useAppStore((s) => s.addNotification)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const parsed = specialties.split(',').map((s) => s.trim()).filter(Boolean)
|
||||
if (!name || !email || !bio || parsed.length === 0) return alert('All fields required and at least one specialty')
|
||||
if (!name || !email || !bio || parsed.length === 0) {
|
||||
addNotification('All fields required and at least one specialty', 'error')
|
||||
return
|
||||
}
|
||||
const newUser = createUser({ name, email, bio, specialties: parsed })
|
||||
navigate(`/profile/${newUser.id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h2>Create User</h2>
|
||||
<form onSubmit={onSubmit}>
|
||||
<div>
|
||||
<label>Name</label>
|
||||
<input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Email</label>
|
||||
<input value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Bio</label>
|
||||
<input value={bio} onChange={(e) => setBio(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label>Specialties (comma separated)</label>
|
||||
<input value={specialties} onChange={(e) => setSpecialties(e.target.value)} />
|
||||
</div>
|
||||
<div style={{marginTop:8}}>
|
||||
<button className="button" type="submit">Create</button>
|
||||
<div className="create-account-bg" style={{minHeight:'100vh',display:'flex',alignItems:'center',justifyContent:'center'}}>
|
||||
<div className="create-card">
|
||||
<h2>Create Account</h2>
|
||||
<form onSubmit={onSubmit} style={{display:'flex',flexDirection:'column',gap:12}}>
|
||||
<input className="input" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
<input className="input" placeholder="E-mail" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
<input type="password" className="input" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
<textarea className="input" placeholder="Short bio" value={bio} onChange={(e) => setBio(e.target.value)} />
|
||||
<input className="input" placeholder="Specialties (comma separated)" value={specialties} onChange={(e) => setSpecialties(e.target.value)} />
|
||||
<div style={{display:'flex',justifyContent:'center',marginTop:8}}>
|
||||
<button className="btn-gradient" type="submit">SIGN UP</button>
|
||||
<button type="button" className="btn-ghost" onClick={() => navigate('/')}>SIGN IN</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@ const Home: React.FC = () => {
|
||||
const currentUserId = useAppStore((s) => s.currentUserId)
|
||||
const suggestions = users.filter((u) => u.id !== currentUserId).slice(0, 3)
|
||||
|
||||
const toggleCreatePost = useAppStore((s) => s.toggleCreatePost)
|
||||
|
||||
return (
|
||||
<div className="feed-shell">
|
||||
<div className="container main-card" style={{display:'flex',gap:20,alignItems:'flex-start'}}>
|
||||
@@ -28,14 +30,15 @@ const Home: React.FC = () => {
|
||||
<div className="small">Recent · Friends · Popular</div>
|
||||
</header>
|
||||
|
||||
<Feed />
|
||||
|
||||
<div className="create-input card" style={{marginTop:12}}>
|
||||
<input placeholder="Share something..." style={{width:'100%',border:'none',outline:'none'}} />
|
||||
<div className="create-input card" style={{marginBottom:12}}>
|
||||
<input placeholder="Share something..." style={{width:'100%',border:'none',outline:'none'}} onFocus={() => toggleCreatePost()} />
|
||||
<div style={{display:'flex',justifyContent:'flex-end',marginTop:8}}>
|
||||
<button className="button">Send</button>
|
||||
<button className="button" onClick={() => toggleCreatePost()}>Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Feed />
|
||||
|
||||
</main>
|
||||
|
||||
<aside className="right-column">
|
||||
|
||||
@@ -1,25 +1,74 @@
|
||||
import React from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import React, { useState } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import useAppStore from '../store/useAppStore'
|
||||
import PDFPreview from '../components/PDFPreview'
|
||||
import MarkdownPreview from '../components/MarkdownPreview'
|
||||
import { generateToken } from '../utils/fileHelpers'
|
||||
|
||||
const PostDetail: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const post = useAppStore((s) => s.posts.find((p) => p.id === id))
|
||||
const author = useAppStore((s) => s.users.find((u) => u.id === post?.authorId))
|
||||
const endorsePost = useAppStore((s) => s.endorsePost)
|
||||
const currentUserId = useAppStore((s) => s.currentUserId)
|
||||
const addNotification = useAppStore((s) => s.addNotification)
|
||||
|
||||
const [shareUrl, setShareUrl] = useState<string | null>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
if (!post) return <div className="card">Post not found</div>
|
||||
|
||||
const handleEndorse = async () => {
|
||||
if (!currentUserId) {
|
||||
addNotification('Select a current user (top-right) to endorse from.', 'error')
|
||||
return
|
||||
}
|
||||
if (currentUserId === post.authorId) {
|
||||
addNotification("You can't endorse your own post.", 'error')
|
||||
return
|
||||
}
|
||||
endorsePost(post.id)
|
||||
const token = generateToken(6)
|
||||
const url = `https://arxiv.org/auth/endorse?x=${token}`
|
||||
setShareUrl(url)
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
setCopied(true)
|
||||
addNotification('Share link copied to clipboard', 'success')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = async () => {
|
||||
if (!shareUrl) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(shareUrl)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
addNotification('Share link copied to clipboard', 'success')
|
||||
} catch {
|
||||
addNotification('Could not copy share link', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div style={{marginBottom:12}}>
|
||||
<Link to="/">← Home</Link>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h3>{author?.name}</h3>
|
||||
<div className="small">{author?.bio}</div>
|
||||
<div style={{marginTop:8}}>{post.content}</div>
|
||||
{post.attachedPDF && <PDFPreview url={post.attachedPDF.url} name={post.attachedPDF.name} />}
|
||||
{post.attachedMarkdown && <MarkdownPreview content={post.attachedMarkdown.content} />}
|
||||
<div style={{marginTop:8}}>
|
||||
<button className="button" onClick={() => endorsePost(post.id)}>Endorse Post ({post.endorsements})</button>
|
||||
<button className="button" onClick={handleEndorse}>Endorse Post ({post.endorsements})</button>
|
||||
{shareUrl && (
|
||||
<div className="small" style={{marginTop:8}}>
|
||||
Share link: <a href={shareUrl} target="_blank" rel="noreferrer">{shareUrl}</a>
|
||||
<button style={{marginLeft:8}} onClick={handleCopy}>{copied ? 'Copied' : 'Copy'}</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,13 +13,30 @@ const Profile: React.FC = () => {
|
||||
const currentUserId = useAppStore((s) => s.currentUserId)
|
||||
const endorsementHistory = useAppStore((s) => s.endorsementHistory)
|
||||
const allUsers = useAppStore((s) => s.users)
|
||||
const addNotification = useAppStore((s) => s.addNotification)
|
||||
|
||||
if (!user) return <div className="card">User not found</div>
|
||||
|
||||
const onEmailClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (!currentUserId) {
|
||||
addNotification('Select a current user (top-right) to endorse from.', 'error')
|
||||
return
|
||||
}
|
||||
if (currentUserId === user.id) {
|
||||
addNotification("You can't endorse yourself.", 'error')
|
||||
return
|
||||
}
|
||||
const specialty = user.specialties[0] ?? 'General'
|
||||
endorseUser(user.id, specialty)
|
||||
addNotification(`Endorsed ${user.name} for ${specialty}`, 'success')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h2>{user.name}</h2>
|
||||
<div className="small"><a href="#" onClick={onEmailClick}>{user.email}</a></div>
|
||||
<div className="small">{user.bio}</div>
|
||||
<div style={{marginTop:8}}>
|
||||
{user.specialties.map((s) => (
|
||||
|
||||
Reference in New Issue
Block a user