Files
crowd-fund-web/src/components/project/ProjectUpdateCard.svelte
T

163 lines
5.3 KiB
Svelte

<script lang="ts">
import { twMerge } from "tailwind-merge";
import Bullet from "../../components/icons/Bullet.svelte";
import { t } from "../../i18n/store";
import { locale } from "../../i18n/store";
import { apiUsersIdOrHandleGet, type ProjectUpdate } from "../../openapi/client/index";
import { formatDate } from "../../utils/dates.ts";
import { extractId } from "../../utils/extractId.ts";
import { renderMarkdown } from "../../utils/renderMarkdown";
import Button from "../library/Button.svelte";
import type { User } from "../../openapi/client/types.gen.ts";
import type { MouseEventHandler } from "svelte/elements";
export type ProjectUpdateCardType = "contracted" | "expanded" | "mobile";
const cardStyles = {
contracted: "h-[24.5rem] w-[21.75rem] gap-6",
expanded: "h-[24.5rem] w-[30.75rem] gap-6",
mobile: "h-[25.625rem] w-[13.5rem] gap-3",
};
const mediaGroupStyles = {
contracted: "flex flex-col gap-4",
expanded: "flex shrink-0 flex-col gap-4",
mobile: "flex shrink-0 flex-col gap-3",
};
const dateStyles = {
contracted: "shrink-0 text-2xl",
expanded: "text-2xl",
mobile: "text-2xl",
};
const imageStyles = {
contracted: "rounded-3xl",
expanded: "rounded-3xl",
mobile: "rounded-2xl",
};
const contentGroupStyles = {
contracted: "gap-4",
expanded: "gap-4",
mobile: "gap-2",
};
const titleStyles = {
contracted: "line-clamp-1 text-double leading-7",
expanded: "line-clamp-1 text-xl leading-7",
mobile: "line-clamp-2 text-lg leading-5",
};
const bodyStyles = {
contracted: "text-base text-ellipsis",
expanded: "text-base text-ellipsis",
mobile: "text-[0.6875rem]",
};
const footerStyles = {
contracted: "items-end justify-between gap-3",
expanded: "items-end justify-between gap-3",
mobile: "flex-col gap-3",
};
const authorStyles = {
contracted: "line-clamp-1 text-xs",
expanded: "text-xs",
mobile: "line-clamp-1 text-center text-[0.6875rem]",
};
interface Props {
update: ProjectUpdate;
type?: ProjectUpdateCardType;
onClick?: MouseEventHandler<HTMLButtonElement> | undefined;
isActive?: boolean;
}
let { update, type = "contracted", onClick, isActive }: Props = $props();
let author: User | undefined = $state(undefined);
async function getAuthor(update: ProjectUpdate): Promise<User | undefined> {
const authorId: string | null = extractId(update.author!);
if (!authorId) return undefined;
const { data: user, error: err } = await apiUsersIdOrHandleGet({
path: { idOrHandle: authorId },
});
if (err) {
throw new Error(err.description!);
}
return user;
}
$effect(() => {
getAuthor(update).then((data) => (author = data));
});
</script>
<div
class={twMerge(
"flex shrink-0 flex-col overflow-hidden rounded-[1.25rem] border border-[#E7E1F1] bg-[#FCFAFF] p-4 shadow-sm transition-[width,opacity,box-shadow] duration-300 ease-out",
cardStyles[type],
isActive && "opacity-100",
)}
>
<div class={mediaGroupStyles[type]}>
<div
class={twMerge(
"text-secondary flex flex-row gap-0.5 leading-6 font-bold",
dateStyles[type],
)}
>
{#if update.date}
{formatDate(new Date(update.date), $locale)}
{/if}
<div class="pt-0.5">
<Bullet />
</div>
</div>
{#if update.cover}
<img
src={update.cover}
alt={update.title}
class={twMerge(
"no-select h-[268.6px] w-full shrink-0 self-stretch object-cover",
imageStyles[type],
)}
draggable="false"
/>
{/if}
</div>
<div class="flex min-h-0 flex-1 flex-col justify-between gap-6">
<div class={twMerge("flex flex-col", contentGroupStyles[type])}>
<h2 class={twMerge("text-secondary font-bold", titleStyles[type])}>
{update.title}
</h2>
{#if update.subtitle || update.body}
<div class="flex flex-col gap-2 leading-5">
<p class="line-clamp-2 text-base font-bold text-black">{update.subtitle}</p>
<p class={twMerge("text-content line-clamp-2 font-normal", bodyStyles[type])}>
{#await renderMarkdown(update.body) then content}
{@html content}
{/await}
</p>
</div>
{/if}
</div>
<div class={twMerge("flex w-full", footerStyles[type])}>
<span class={twMerge("text-content font-medium", authorStyles[type])}>
{$t("pages.project.view.tabs.updates.by")}
<strong class="font-bold text-black"> {author?.displayName}</strong>
</span>
<Button kind="ghost" onclick={onClick}>
{$t("pages.project.view.tabs.updates.content.btn.readMore")}
</Button>
</div>
</div>
</div>