✨ Personal Page
This commit is contained in:
		
							
								
								
									
										3
									
								
								pkg/view/src/components/NameCard.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								pkg/view/src/components/NameCard.module.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| .description { | ||||
|     color: var(--fallback-bc, oklch(var(--bc)/.8)); | ||||
| } | ||||
							
								
								
									
										58
									
								
								pkg/view/src/components/NameCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								pkg/view/src/components/NameCard.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import { createSignal } from "solid-js"; | ||||
|  | ||||
| import styles from "./NameCard.module.css"; | ||||
|  | ||||
| export default function NameCard(props: { accountId: number, onError: (messasge: string | null) => void }) { | ||||
|   const [info, setInfo] = createSignal<any>(null); | ||||
|  | ||||
|   const [_, setLoading] = createSignal(true); | ||||
|  | ||||
|   async function readInfo() { | ||||
|     setLoading(true); | ||||
|     const res = await fetch(`/api/users/${props.accountId}`); | ||||
|     if (res.status !== 200) { | ||||
|       props.onError(await res.text()); | ||||
|     } else { | ||||
|       setInfo(await res.json()); | ||||
|       props.onError(null); | ||||
|     } | ||||
|     setLoading(false); | ||||
|   } | ||||
|  | ||||
|   readInfo(); | ||||
|  | ||||
|   return ( | ||||
|     <div class="relative"> | ||||
|       <figure id="banner"> | ||||
|         <img class="object-cover w-full h-36" src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b" | ||||
|              alt="banner" /> | ||||
|       </figure> | ||||
|  | ||||
|       <div id="avatar" class="avatar absolute border-4 border-base-200 left-[20px] top-[4.5rem]"> | ||||
|         <div class="w-24"> | ||||
|           <img src={info()?.avatar} alt="avatar" /> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div id="actions" class="flex justify-end"> | ||||
|         <div> | ||||
|           <button type="button" class="btn btn-primary"> | ||||
|             <i class="fa-solid fa-plus"></i> | ||||
|             Follow | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div id="description" class="px-6 pb-7"> | ||||
|         <h2 class="text-2xl font-bold">{info()?.name}</h2> | ||||
|         <p class="text-md">{info()?.description}</p> | ||||
|         <div class={`mt-2 ${styles.description}`}> | ||||
|           <p class="text-xs"> | ||||
|             <i class="fa-solid fa-calendar-days me-2"></i> | ||||
|             Joined at {new Date(info()?.created_at).toLocaleString()} | ||||
|           </p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @@ -24,22 +24,24 @@ export default function PostItem(props: { post: any, onError: (message: string | | ||||
|   return ( | ||||
|     <div class="post-item"> | ||||
|  | ||||
|       <div class="flex bg-base-200"> | ||||
|         <div class="avatar"> | ||||
|           <div class="w-12"> | ||||
|             <Show when={props.post.author.avatar} | ||||
|                   fallback={<span class="text-3xl">{props.post.author.name.substring(0, 1)}</span>}> | ||||
|               <img alt="avatar" src={props.post.author.avatar} /> | ||||
|             </Show> | ||||
|       <a href={`/accounts/${props.post.author.id}`}> | ||||
|         <div class="flex bg-base-200"> | ||||
|           <div class="avatar pl-[20px]"> | ||||
|             <div class="w-12"> | ||||
|               <Show when={props.post.author.avatar} | ||||
|                     fallback={<span class="text-3xl">{props.post.author.name.substring(0, 1)}</span>}> | ||||
|                 <img alt="avatar" src={props.post.author.avatar} /> | ||||
|               </Show> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="flex items-center px-5"> | ||||
|             <div> | ||||
|               <h3 class="font-bold text-sm">{props.post.author.name}</h3> | ||||
|               <p class="text-xs">{props.post.author.description}</p> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="flex items-center px-5"> | ||||
|           <div> | ||||
|             <h3 class="font-bold text-sm">{props.post.author.name}</h3> | ||||
|             <p class="text-xs">{props.post.author.description}</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       </a> | ||||
|  | ||||
|       <article class="py-5 px-7"> | ||||
|         <h2 class="card-title">{props.post.title}</h2> | ||||
| @@ -48,7 +50,7 @@ export default function PostItem(props: { post: any, onError: (message: string | | ||||
|  | ||||
|       <div class="grid grid-cols-3 border-y border-base-200"> | ||||
|  | ||||
|         <div class="col-span-2 grid grid-cols-4"> | ||||
|         <div class="grid grid-cols-2"> | ||||
|           <div class="tooltip" data-tip="Daisuki"> | ||||
|             <button type="button" class="btn btn-ghost btn-block" disabled={reacting()} | ||||
|                     onClick={() => reactPost(props.post, "like")}> | ||||
| @@ -64,7 +66,9 @@ export default function PostItem(props: { post: any, onError: (message: string | | ||||
|               <code class="font-mono">{props.post.dislike_count}</code> | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="col-span-2 flex justify-end"> | ||||
|           <div class="tooltip" data-tip="Reply"> | ||||
|             <button type="button" class="btn btn-ghost btn-block"> | ||||
|               <i class="fa-solid fa-reply"></i> | ||||
| @@ -76,9 +80,7 @@ export default function PostItem(props: { post: any, onError: (message: string | | ||||
|               <i class="fa-solid fa-retweet"></i> | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="flex justify-end"> | ||||
|           <div class="dropdown dropdown-end"> | ||||
|             <div tabIndex="0" role="button" class="btn btn-ghost w-12"> | ||||
|               <i class="fa-solid fa-ellipsis-vertical"></i> | ||||
|   | ||||
| @@ -1,33 +1,24 @@ | ||||
| import { createMemo, createSignal, For, Show } from "solid-js"; | ||||
|  | ||||
| import styles from "./PostList.module.css"; | ||||
|  | ||||
| import PostPublish from "./PostPublish.tsx"; | ||||
| import PostItem from "./PostItem.tsx"; | ||||
|  | ||||
| export default function PostList(props: { onError: (message: string | null) => void }) { | ||||
| export default function PostList(props: { | ||||
|   info: { data: any[], count: number } | null, | ||||
|   onUpdate: (pn: number) => Promise<void>, | ||||
|   onError: (message: string | null) => void | ||||
| }) { | ||||
|   const [loading, setLoading] = createSignal(true); | ||||
|  | ||||
|   const [posts, setPosts] = createSignal<any[]>([]); | ||||
|   const [postCount, setPostCount] = createSignal(0); | ||||
|   const posts = createMemo(() => props.info?.data) | ||||
|   const postCount = createMemo<number>(() => props.info?.count ?? 0) | ||||
|  | ||||
|   const [page, setPage] = createSignal(1); | ||||
|   const pageCount = createMemo(() => Math.ceil(postCount() / 10)); | ||||
|  | ||||
|   async function readPosts() { | ||||
|     setLoading(true); | ||||
|     const res = await fetch("/api/posts?" + new URLSearchParams({ | ||||
|       take: (10).toString(), | ||||
|       offset: ((page() - 1) * 10).toString() | ||||
|     })); | ||||
|     if (res.status !== 200) { | ||||
|       props.onError(await res.text()); | ||||
|     } else { | ||||
|       const data = await res.json(); | ||||
|       setPosts(data["data"]); | ||||
|       setPostCount(data["count"]); | ||||
|       props.onError(null); | ||||
|     } | ||||
|     await props.onUpdate(page()); | ||||
|     setLoading(false); | ||||
|   } | ||||
|  | ||||
| @@ -42,8 +33,6 @@ export default function PostList(props: { onError: (message: string | null) => v | ||||
|  | ||||
|   return ( | ||||
|     <div id="post-list"> | ||||
|       <PostPublish onPost={() => readPosts()} onError={props.onError} /> | ||||
|  | ||||
|       <div id="posts"> | ||||
|         <For each={posts()}> | ||||
|           {item => <PostItem post={item} onReact={() => readPosts()} onError={props.onError} />} | ||||
|   | ||||
| @@ -20,7 +20,10 @@ render(() => ( | ||||
|   <WellKnownProvider> | ||||
|     <UserinfoProvider> | ||||
|       <Router root={RootLayout}> | ||||
|         <Route path="/" component={lazy(() => import("./pages/feed.tsx"))} /> | ||||
|         <Route path="/" component={lazy(() => import("./pages/feed.tsx"))}> | ||||
|           <Route path="/" component={lazy(() => import("./pages/global.tsx"))} /> | ||||
|           <Route path="/accounts/:accountId" component={lazy(() => import("./pages/account.tsx"))} /> | ||||
|         </Route> | ||||
|         <Route path="/auth" component={lazy(() => import("./pages/auth/callout.tsx"))} /> | ||||
|         <Route path="/auth/callback" component={lazy(() => import("./pages/auth/callback.tsx"))} /> | ||||
|       </Router> | ||||
|   | ||||
							
								
								
									
										50
									
								
								pkg/view/src/pages/account.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								pkg/view/src/pages/account.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import { createSignal, Show } from "solid-js"; | ||||
| import { useParams } from "@solidjs/router"; | ||||
|  | ||||
| import PostList from "../components/PostList.tsx"; | ||||
| import NameCard from "../components/NameCard.tsx"; | ||||
|  | ||||
| export default function DashboardPage() { | ||||
|   const [error, setError] = createSignal<string | null>(null); | ||||
|  | ||||
|   const [page, setPage] = createSignal(0); | ||||
|   const [info, setInfo] = createSignal<any>(null); | ||||
|  | ||||
|   const params = useParams(); | ||||
|  | ||||
|   async function readPosts(pn?: number) { | ||||
|     if (pn) setPage(pn); | ||||
|     const res = await fetch("/api/posts?" + new URLSearchParams({ | ||||
|       take: (10).toString(), | ||||
|       offset: ((page() - 1) * 10).toString(), | ||||
|       authorId: params["accountId"] | ||||
|     })); | ||||
|     if (res.status !== 200) { | ||||
|       setError(await res.text()); | ||||
|     } else { | ||||
|       setError(null); | ||||
|       setInfo(await res.json()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div id="alerts"> | ||||
|         <Show when={error()}> | ||||
|           <div role="alert" class="alert alert-error"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||
|                  viewBox="0 0 24 24"> | ||||
|               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||
|                     d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||
|             </svg> | ||||
|             <span class="capitalize">{error()}</span> | ||||
|           </div> | ||||
|         </Show> | ||||
|       </div> | ||||
|  | ||||
|       <NameCard accountId={parseInt(params["accountId"])} onError={setError} /> | ||||
|  | ||||
|       <PostList info={info()} onUpdate={readPosts} onError={setError} /> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| @@ -1,33 +1,12 @@ | ||||
| import { createEffect, createSignal, For, Show } from "solid-js"; | ||||
|  | ||||
| import styles from "./feed.module.css"; | ||||
|  | ||||
| import PostList from "../components/PostList.tsx"; | ||||
|  | ||||
| export default function DashboardPage() { | ||||
|   const [error, setError] = createSignal<string | null>(null); | ||||
|  | ||||
| export default function DashboardPage(props: any) { | ||||
|   return ( | ||||
|     <div class={`${styles.wrapper} container mx-auto`}> | ||||
|       <div id="trending" class="card shadow-xl h-fit"></div> | ||||
|  | ||||
|       <div id="content" class="card shadow-xl"> | ||||
|  | ||||
|         <div id="alerts"> | ||||
|           <Show when={error()}> | ||||
|             <div role="alert" class="alert alert-error"> | ||||
|               <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||
|                    viewBox="0 0 24 24"> | ||||
|                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||
|                       d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||
|               </svg> | ||||
|               <span class="capitalize">{error()}</span> | ||||
|             </div> | ||||
|           </Show> | ||||
|         </div> | ||||
|          | ||||
|         <PostList onError={setError} /> | ||||
|  | ||||
|         {props.children} | ||||
|       </div> | ||||
|  | ||||
|       <div id="well-known" class="card shadow-xl h-fit"></div> | ||||
|   | ||||
							
								
								
									
										46
									
								
								pkg/view/src/pages/global.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pkg/view/src/pages/global.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import { createSignal, Show } from "solid-js"; | ||||
|  | ||||
| import PostList from "../components/PostList.tsx"; | ||||
| import PostPublish from "../components/PostPublish.tsx"; | ||||
|  | ||||
| export default function DashboardPage() { | ||||
|   const [error, setError] = createSignal<string | null>(null); | ||||
|  | ||||
|   const [page, setPage] = createSignal(0); | ||||
|   const [info, setInfo] = createSignal<any>(null); | ||||
|  | ||||
|   async function readPosts(pn?: number) { | ||||
|     if (pn) setPage(pn); | ||||
|     const res = await fetch("/api/posts?" + new URLSearchParams({ | ||||
|       take: (10).toString(), | ||||
|       offset: ((page() - 1) * 10).toString() | ||||
|     })); | ||||
|     if (res.status !== 200) { | ||||
|       setError(await res.text()); | ||||
|     } else { | ||||
|       setError(null); | ||||
|       setInfo(await res.json()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div id="alerts"> | ||||
|         <Show when={error()}> | ||||
|           <div role="alert" class="alert alert-error"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||
|                  viewBox="0 0 24 24"> | ||||
|               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||
|                     d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||
|             </svg> | ||||
|             <span class="capitalize">{error()}</span> | ||||
|           </div> | ||||
|         </Show> | ||||
|       </div> | ||||
|  | ||||
|       <PostPublish onPost={() => readPosts()} onError={setError} /> | ||||
|  | ||||
|       <PostList info={info()} onUpdate={readPosts} onError={setError} /> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user