This commit is contained in:
x1ulan 2025-06-10 22:10:01 +08:00
parent b61998b9e3
commit 868b5e1c36
6 changed files with 126 additions and 61 deletions

View file

@ -49,28 +49,30 @@ export const Delete = () => {
<form <form
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className= className=
'flex flex-col m-auto w-10/12 h-[80dvh] px-10 gap-3 pb-3 md:w-[50dvw]' 'flex flex-col m-auto w-10/12 px-6 py-8 gap-6 rounded-lg shadow-xl bg-white md:w-[50dvw]'
> >
{/* register your input into the hook by invoking the "register" function */} <h1 className="text-4xl font-bold text-gray-800 mb-4 text-center"></h1>
<h1 className="text-3xl my-3"></h1> <label htmlFor='id' className='text-lg font-medium text-gray-700'>
<label htmlFor='id' className='text-left'>
ID: ID:
</label> </label>
<input id="id" type="search" {...register("id", { required: true })} className='border-b-4 border-zinc-500 w-full text-end outline-none focus:border-zinc-200' list="saved_id" /> <input id="id" type="search" {...register("id", { required: true })} className='border-b-4 border-zinc-500 w-full text-end outline-none focus:border-gray-600 text-black' list="saved_id" />
<datalist id="saved_id"> <datalist id="saved_id">
{saved.map((e, it) => <option value={e.id} key={it}></option>)} {saved.map((e, it) => <option value={e.id} key={it}></option>)}
</datalist> </datalist>
<label htmlFor='hash'> <label htmlFor='hash' className='text-lg font-medium text-gray-700'>
Hash: Hash:
</label> </label>
<input id="hash" type="text" {...register("hash", { required: true })} className='border-b-4 border-zinc-500 w-full text-end outline-none focus:border-zinc-200' /> <input id="hash" type="text" {...register("hash", { required: true })} className='border-b-4 border-zinc-500 w-full text-end outline-none focus:border-gray-600 text-black' />
{/* errors will return when field validation fails */} {/* errors will return when field validation fails */}
{errors.id && <span>This field is required</span>} {errors.id && <span>This field is required</span>}
{errors.hash && <span>This field is required</span>} {errors.hash && <span>This field is required</span>}
<button type="submit" className="ring-3 w-full mx-auto rounded-md "> <button
type="submit"
className="mt-6 w-full py-3 bg-gray-800 text-white font-semibold rounded-lg hover:bg-gray-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" // Consistent button styling
>
</button> </button>
</form> </form>

View file

@ -14,27 +14,28 @@ export const Fetch = () => {
} }
) )
}, []) }, [])
return (<div className="h-dvh w-dvw flex flex-col justify-items-center overflow-scroll gap-10 pt-10 pb-10"> return (<div className="min-h-dvh w-dvw flex flex-col items-center gap-6 py-8 px-4">
{fetched.map(post => <div className="w-[87%] h-fit mx-auto border-1 border-slate-700 rounded"> {fetched.map(post => <div className="w-full max-w-2xl bg-gray-200 rounded-lg shadow-md p-6 flex flex-col gap-4">
<p className="text-left mx-2 mt-2">{post.content}</p> <p className="text-gray-800 text-base leading-relaxed">{post.content}</p>
{post.enclosure.length > 0 && post.enclosure[0] != null && <div className="flex flex-row overflow-scroll w-full h-30 mx-2"> {post.enclosure.length > 0 && post.enclosure[0] != null && <div className="flex flex-row overflow-x-auto gap-3 py-2 -mx-2 px-2 border-t border-gray-200 pt-4">
{post.enclosure.map(enc => { {post.enclosure.map((enc, index) => {
if (enc) { if (enc) {
return <img src={MediaPathTrc(enc)} className="rounded h-full w-auto" /> return <img
key={index}
src={MediaPathTrc(enc)}
alt={`Post media ${post.id}-${index}`}
className="rounded-md h-32 object-cover aspect-video flex-shrink-0"
/>
} else { } else {
return "" return ""
} }
})} })}
</div>} </div>}
{post.signing && <p className="text-right mr-3">by: {post.signing}</p>} {post.signing && <p className="text-right text-sm text-gray-600 italic mt-2">by: {post.signing}</p>}
<div className="h-fit flex flex-row text-sm mx-3"> <div className="flex justify-between items-center text-xs text-gray-500 border-t border-gray-100 pt-3">
<p className="self-start">PID: {post.id}</p> <p className="self-start">PID: {post.id}</p>
<p className="self-end ml-auto">: {post.post_at.toLocaleTimeString()}</p> <p className="self-end">: {post.post_at.toLocaleTimeString()}</p>
</div> </div>
</div>)} </div>)}
</div>) </div>)
} }

View file

@ -12,7 +12,7 @@ const ImageWithPreview = ({
<> <>
<img <img
src={src} src={src}
alt="kkk" alt="tyts"
onClick={() => { onClick={() => {
setShow(true) setShow(true)
}} }}
@ -25,11 +25,11 @@ const ImageWithPreview = ({
} }
onClick={() => setShow(false)} onClick={() => setShow(false)}
> >
<div className="z-20 w-full fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 md:w-[50dvw]"> <div className="z-20 w-full md:w-fit fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 md:w-[50dvw]">
<img src={src} className="w-[90dvw] h-auto m-auto" /> <img src={src} className="w-[90dvw] h-auto m-auto md:w-auto md:max-h-[80dvh]" />
<button <button
type="button" type="button"
className="border w-full" className="mt-6 w-full py-3 bg-gray-800 text-white font-semibold rounded-lg hover:bg-gray-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" // Consistent button styling
onClick={() => remove()} onClick={() => remove()}
> >

View file

@ -1,4 +1,4 @@
import { useState, type ReactElement, } from 'react' import { useState, type ReactElement } from 'react'
import { useForm, type SubmitHandler } from 'react-hook-form' import { useForm, type SubmitHandler } from 'react-hook-form'
import ImageWithPreview from './ImageWithPrview' import ImageWithPreview from './ImageWithPrview'
import { Post } from './api' import { Post } from './api'
@ -77,20 +77,20 @@ export default function PostForm() {
<form <form
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
className={ className={
'flex flex-col m-auto w-10/12 h-[80dvh] px-10 gap-3 pb-3 md:w-[50dvw]' + 'flex flex-col m-auto w-10/12 h-[80dvh] px-6 py-8 gap-6 rounded-lg shadow-xl bg-white ' + // Added background, padding, shadow, rounded corners
(cred !== undefined ? ' hidden' : '') (cred !== undefined ? ' hidden' : '')
} }
> >
{/* register your input into the hook by invoking the "register" function */} <h1 className="text-4xl font-bold text-gray-800 mb-4 text-center"></h1> {/* Larger, bolder title, centered */}
<h1 className="text-3xl my-3"></h1>
<textarea <textarea
className="ring-3 ring-zinc-500 focus:ring-zinc-200 outline-none rounded-xl h-5/12 px-3 w-full" className="p-4 border border-gray-300 rounded-lg h-5/12 w-full resize-y focus:outline-none focus:border-gray-500 transition-colors duration-200 text-black" // Modernized textarea
placeholder="匿名文章" placeholder="匿名文章"
{...register('content', { required: true })} {...register('content', { required: true })}
/> />
<div> <div>
<label htmlFor="files" className="text-xl"> <label htmlFor="files" className="block text-lg font-medium text-gray-700 mb-2 cursor-pointer"> {/* Styled label */}
<span className="ml-2 px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm hover:bg-gray-200 transition-colors duration-200"></span>
</label> </label>
<input <input
multiple multiple
@ -102,7 +102,7 @@ export default function PostForm() {
/> />
<div <div
className={ className={
'h-20 w-full gap-3 overflow-scroll flex flex-row ' + 'h-24 w-full gap-3 overflow-x-auto flex flex-row items-center border border-gray-200 rounded-lg p-2 ' + // Adjusted image preview container
(files_list && files_list.length > 0 ? '' : 'hidden') (files_list && files_list.length > 0 ? '' : 'hidden')
} }
> >
@ -110,20 +110,22 @@ export default function PostForm() {
</div> </div>
</div> </div>
{/* include validation with required or other standard HTML validation rules */} <div className="ml-auto flex items-center gap-3"> {/* Aligned items, added items-center */}
<div className="ml-auto flex gap-3"> <label className="w-fit text-lg font-medium text-gray-700 break-keep"></label>
<label className="w-fit break-keep"></label>
<input <input
className="border-b-4 border-zinc-500 w-full text-end outline-none focus:border-zinc-200" className="border-b-2 border-gray-400 w-full text-right outline-none focus:border-gray-600 transition-colors duration-200 p-1" // Modernized signing input
{...register('signing', { {...register('signing', {
maxLength: 20, maxLength: 20,
})} })}
/> />
</div> </div>
{/* errors will return when field validation fails */} {errors.content && <span className="text-red-500 text-sm"></span>} {/* Styled error message */}
{errors.content && <span>This field is required</span>}
<button type="submit" className="ring-3 w-full mx-auto rounded-md " disabled={posting}> <button
type="submit"
className="mt-6 w-full py-3 bg-gray-800 text-white font-semibold rounded-lg hover:bg-gray-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" // Modernized button
disabled={posting}
>
</button> </button>
</form> </form>

View file

@ -2,21 +2,81 @@ import { useState } from 'react'
export const Zone = ({ setZone }: { setZone: (zone_name: string) => void }) => { export const Zone = ({ setZone }: { setZone: (zone_name: string) => void }) => {
const [toggle, setToggle] = useState(false) const [toggle, setToggle] = useState(false)
return ( return (
<> <>
{/* Backdrop with fade animation */}
<div
className={`fixed inset-0 z-10 backdrop-blur-sm transition-all duration-300 ${
toggle
? 'bg-black/50 opacity-100 pointer-events-auto'
: 'bg-black/0 opacity-0 pointer-events-none'
}`}
onClick={() => setToggle(false)}
/>
{toggle && <div className="w-full fixed h-dvh top-0 left-0 z-10 backdrop-brightness-50" > {/* Main drawer */}
</div>} <div className={`
fixed bottom-0 left-1/2 -translate-x-1/2 z-20
w-[min(80dvw,400px)] bg-zinc-800
rounded-t-3xl shadow-2xl border-t border-zinc-600
transition-all duration-500 ease-out
${toggle ? 'h-[45dvh]' : 'h-16'}
`}>
<div className={"fixed bottom-0 left-1/2 -translate-x-1/2 z-20 w-[80dvw] bg-zinc-700 rounded transition-all duration-300" + (toggle ? " h-[40dvh]" : "h-fit")}> {/* Drawer handle/button */}
<button onClick={() => setToggle(!toggle)}>Drawer</button> <div className="relative">
{toggle && <div className="h-[40dvh] w-full flex flex-col pt-10 gap-8 text-xl"> {/* Visual handle indicator */}
<button onClick={() => { setZone("post"); setToggle(false) }}>PO文</button> <div className="w-12 h-1 bg-zinc-500 rounded-full mx-auto mt-3 mb-2" />
<button onClick={() => { setZone("delete"); setToggle(false) }}></button>
<button onClick={() => { setZone("view"); setToggle(false) }}></button> <button
</div>} onClick={() => setToggle(!toggle)}
className="w-full py-3 px-6 text-white font-medium text-lg
hover:bg-zinc-700 transition-colors duration-200
flex items-center justify-center gap-2"
>
<span></span>
<span className={`transition-transform duration-300 ${toggle ? 'rotate-180' : ''}`}>
</span>
</button>
</div>
{/* Menu content with staggered animations */}
<div className={`
px-6 pb-6 overflow-hidden
transition-all duration-500 ease-out
${toggle ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4 pointer-events-none'}
`}>
<div className="grid gap-4 pt-4">
{[
{ key: 'post', label: 'PO文', icon: '✏️', delay: '100ms' },
{ key: 'delete', label: '刪文', icon: '🗑️', delay: '200ms' },
{ key: 'view', label: '查看', icon: '👁️', delay: '300ms' }
].map(({ key, label, icon, delay }) => (
<button
key={key}
onClick={() => { setZone(key); setToggle(false) }}
className={`
w-full py-4 px-6 bg-zinc-700 hover:bg-zinc-600
rounded-xl text-white text-lg font-medium
flex items-center gap-4
transform transition-all duration-300 ease-out
hover:scale-105 hover:shadow-lg
active:scale-95
${toggle ? 'translate-y-0 opacity-100' : 'translate-y-4 opacity-0'}
`}
style={{
transitionDelay: toggle ? delay : '0ms'
}}
>
<span className="text-2xl">{icon}</span>
<span>{label}</span>
</button>
))}
</div>
</div>
</div> </div>
</> </>
) )
} }

View file

@ -1,5 +1,5 @@
import { z } from 'zod/v4' import { z } from 'zod/v4'
let rooturl = 'http://10.16.20.19:3000' let rooturl = 'http://172.16.20.145:3000'
type Inputs = { type Inputs = {
content: string content: string