Commit 3ede79e9 authored by ChanHaeng Lee's avatar ChanHaeng Lee
Browse files

Decorated /users/[identifier]/posts page

parent 6b4cc9e8
Loading
Loading
Loading
Loading
+146 −0
Original line number Diff line number Diff line
@@ -129,4 +129,150 @@ body {
      @apply flex-col items-start gap-1;
    }
  }

  /* Posts Page Styles */
  .post-form {
    @apply mx-auto my-8 max-w-4xl rounded-xl border p-6;
    background: var(--background);
    border-color: rgba(0, 0, 0, 0.1);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  }

  .form-group {
    @apply mb-4;
  }

  .form-label {
    @apply mb-2 block text-lg font-semibold;
    color: var(--foreground);
  }

  .form-textarea {
    @apply w-full resize-none rounded-lg border p-3 text-base focus:ring-2 focus:ring-blue-500 focus:outline-none;
    background: var(--background);
    color: var(--foreground);
    border-color: rgba(0, 0, 0, 0.2);
    transition:
      border-color 0.2s,
      box-shadow 0.2s;
  }

  .form-textarea:focus {
    border-color: #3b82f6;
  }

  .post-button {
    @apply rounded-lg px-6 py-2 font-semibold text-white transition-all duration-200;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }

  .post-button:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  }

  .error-state,
  .loading-state {
    @apply py-12 text-center;
  }

  .error-state p {
    @apply text-lg text-red-600;
  }

  .loading-state p {
    @apply mt-4 text-gray-600;
  }

  .posts-container {
    @apply mx-auto max-w-4xl;
  }

  .posts-title {
    @apply mb-6 text-2xl font-bold;
    color: var(--foreground);
  }

  .posts-grid {
    @apply grid gap-6;
  }

  .post-card {
    @apply rounded-xl border transition-all duration-200 hover:shadow-lg;
    background: var(--background);
    border-color: rgba(0, 0, 0, 0.1);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }

  .post-card:hover {
    transform: translateY(-2px);
  }

  .post-link {
    @apply block p-6 no-underline;
    color: inherit;
  }

  .post-header {
    @apply mb-4 flex items-center gap-3;
  }

  .post-avatar {
    @apply h-12 w-12 rounded-full border-2 border-gray-200 object-cover;
  }

  .post-user-info {
    @apply flex-1;
  }

  .post-user-name {
    @apply mb-1 text-lg font-semibold;
    color: var(--foreground);
  }

  .post-user-handle {
    @apply text-sm opacity-70;
    color: var(--foreground);
  }

  .post-content {
    @apply text-base leading-relaxed;
    color: var(--foreground);
  }

  .post-content p {
    @apply m-0;
  }

  /* Skeleton Styles */
  .skeleton-avatar {
    @apply h-12 w-12 animate-pulse rounded-full bg-gray-300;
  }

  .skeleton-info {
    @apply flex-1 space-y-2;
  }

  .skeleton-line {
    @apply h-4 animate-pulse rounded bg-gray-300;
  }

  .skeleton-name {
    @apply w-24;
  }

  .skeleton-handle {
    @apply w-32;
  }

  @media (max-width: 768px) {
    .posts-container {
      @apply px-4;
    }

    .post-form {
      @apply p-4;
    }
  }
}
+21 −0
Original line number Diff line number Diff line
<svg
  class="mr-3 -ml-1 size-24 animate-spin text-blue-500"
  xmlns="http://www.w3.org/2000/svg"
  fill="none"
  viewBox="0 0 24 24"
>
  <circle
    class="opacity-25"
    cx="12"
    cy="12"
    r="10"
    stroke="currentColor"
    stroke-width="4"
  ></circle>
  <path
    class="opacity-75"
    fill="currentColor"
    d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
  >
  </path>
</svg>
+5 −8
Original line number Diff line number Diff line
@@ -22,14 +22,11 @@ class PostStore {
    return this.#map.get(id.toString());
  }
  get = this.#get.bind(this);
  async #getAll() {
    return await Array.fromAsync(
      this.#timeline.reverse()
  #getAll() {
    return this.#timeline.reverse()
      .map((id) => id.toString())
      .map((id) => this.#map.get(id)!)
        .filter((p) => p)
        .map((p) => p.toJsonLd()),
    );
      .filter((p) => p);
  }
  getAll = this.#getAll.bind(this);
  #delete(id: URL) {
+2 −18
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
  import type { PageProps } from "./$types";
  import { browser } from "$app/environment";
  import type { Person } from "@fedify/fedify";
  import Spinner from "$lib/components/Spinner.svelte";

  let { params }: PageProps = $props();
  const { identifier } = params;
@@ -17,24 +18,7 @@
{#await data}
  <!-- promise is pending -->
  <div class="flex h-svh w-svw items-center justify-center">
    <svg
      class="mr-3 -ml-1 size-24 animate-spin text-blue-500"
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      ><circle
        class="opacity-25"
        cx="12"
        cy="12"
        r="10"
        stroke="currentColor"
        stroke-width="4"
      ></circle><path
        class="opacity-75"
        fill="currentColor"
        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
      ></path></svg
    >
    <Spinner />
  </div>
{:then user}
  {#if user}
+67 −13
Original line number Diff line number Diff line
<script lang="ts">
  import type { PageProps } from "./$types";
  import { browser } from "$app/environment";
  import { getPosts } from "./data.remote";
  import Spinner from "$lib/components/Spinner.svelte";
  import type { Person } from "@fedify/fedify";

  let { params }: PageProps = $props();
  const { identifier } = params;
  const query = getPosts();
  const data = browser
    ? fetch(`/users/${identifier}`, {
        headers: { Accept: "application/activity+json" },
      }).then(
        (res) => res.json() as Promise<Person & { icon: { url: string } }>,
      )
    : Promise.resolve(null);
</script>

<form method="POST" action="?/post">
<form method="POST" action="?/post" class="post-form">
  <input name="identifier" type="hidden" value={identifier} />
  <label>
    Content
    <input name="content" type="text" />
  <div class="form-group">
    <label class="form-label">
      새 포스트 작성
      <textarea
        name="content"
        class="form-textarea"
        placeholder="무엇을 생각하고 계신가요?"
        rows="3"
      ></textarea>
    </label>
  <button>Post</button>
  </div>
  <button type="submit" class="post-button">게시하기</button>
</form>

{#if query.error}
  <p>oops!</p>
  <div class="error-state">
    <p>포스트를 불러오는 중 오류가 발생했습니다.</p>
  </div>
{:else if query.loading}
  <p>loading...</p>
  <div class="loading-state">
    <Spinner />
    <p>포스트를 불러오는 중...</p>
  </div>
{:else if query.current}
  <ul>
  <div class="posts-container">
    <h2 class="posts-title">포스트 목록</h2>
    <div class="posts-grid">
      {#each query.current as note}
      <pre>{JSON.stringify(note, null, 2)}</pre>
        <article class="post-card">
          <a href={note.url} class="post-link">
            <div class="post-header">
              {#await data}
                <div class="skeleton-avatar"></div>
                <div class="skeleton-info">
                  <div class="skeleton-line skeleton-name"></div>
                  <div class="skeleton-line skeleton-handle"></div>
                </div>
              {:then user}
                {#if user}
                  <img
                    src={user.icon?.url ?? "/demo-profile.png"}
                    alt="{user.name}'s profile"
                    class="post-avatar"
                  />
                  <div class="post-user-info">
                    <h3 class="post-user-name">{user.name}</h3>
                    <p class="post-user-handle">
                      @{identifier}@{window.location.host}
                    </p>
                  </div>
                {/if}
              {/await}
            </div>
            <div class="post-content">
              <p>{note.content}</p>
            </div>
          </a>
        </article>
      {/each}
  </ul>
    </div>
  </div>
{/if}