BLOG-126 Post management (list and create) #139
@ -6,7 +6,7 @@ import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
|
|||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type AuthState = AsyncState<AuthViewModel>;
|
type AuthState = AsyncState<AuthViewModel>;
|
||||||
|
|
||||||
export class AuthLoadedStore implements BaseStore<AuthState> {
|
export class AuthLoadedStore implements BaseStore<AuthState> {
|
||||||
private readonly state = writable<AuthState>(AsyncState.idle<AuthViewModel>(null));
|
private readonly state = writable<AuthState>(AsyncState.idle<AuthViewModel>(null));
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import Root from './table.svelte';
|
||||||
|
import Body from './table-body.svelte';
|
||||||
|
import Caption from './table-caption.svelte';
|
||||||
|
import Cell from './table-cell.svelte';
|
||||||
|
import Footer from './table-footer.svelte';
|
||||||
|
import Head from './table-head.svelte';
|
||||||
|
import Header from './table-header.svelte';
|
||||||
|
import Row from './table-row.svelte';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Body,
|
||||||
|
Caption,
|
||||||
|
Cell,
|
||||||
|
Footer,
|
||||||
|
Head,
|
||||||
|
Header,
|
||||||
|
Row,
|
||||||
|
//
|
||||||
|
Root as Table,
|
||||||
|
Body as TableBody,
|
||||||
|
Caption as TableCaption,
|
||||||
|
Cell as TableCell,
|
||||||
|
Footer as TableFooter,
|
||||||
|
Head as TableHead,
|
||||||
|
Header as TableHeader,
|
||||||
|
Row as TableRow,
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tbody
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-body"
|
||||||
|
class={cn('[&_tr:last-child]:border-0', className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</tbody>
|
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<caption
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-caption"
|
||||||
|
class={cn('mt-4 text-sm text-muted-foreground', className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</caption>
|
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLTdAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLTdAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<td
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-cell"
|
||||||
|
class={cn(
|
||||||
|
'bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</td>
|
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tfoot
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-footer"
|
||||||
|
class={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</tfoot>
|
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLThAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLThAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<th
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-head"
|
||||||
|
class={cn(
|
||||||
|
'h-10 bg-clip-padding px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</th>
|
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<thead
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-header"
|
||||||
|
class={cn('[&_tr]:border-b', className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</thead>
|
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tr
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table-row"
|
||||||
|
class={cn(
|
||||||
|
'border-b transition-colors data-[state=selected]:bg-muted hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</tr>
|
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLTableAttributes } from 'svelte/elements';
|
||||||
|
import { cn, type WithElementRef } from '$lib/common/framework/components/utils.js';
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLTableAttributes> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
||||||
|
<table
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="table"
|
||||||
|
class={cn('w-full caption-bottom text-sm', className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</table>
|
||||||
|
</div>
|
@ -6,7 +6,7 @@ import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentU
|
|||||||
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
|
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
|
||||||
import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService';
|
import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService';
|
||||||
import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl';
|
import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl';
|
||||||
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/imageUploadedStore';
|
||||||
import type { ImageRepository } from '$lib/image/application/gateway/imageRepository';
|
import type { ImageRepository } from '$lib/image/application/gateway/imageRepository';
|
||||||
import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
||||||
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
|
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte';
|
import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/imageUploadedStore';
|
||||||
|
|
||||||
const store = getContext<ImageUploadedStore>(ImageUploadedStore.name);
|
const store = getContext<ImageUploadedStore>(ImageUploadedStore.name);
|
||||||
const state = $derived($store);
|
const state = $derived($store);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
||||||
import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
import type { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto';
|
||||||
import type { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
import type { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
||||||
|
|
||||||
export interface PostApiService {
|
export interface PostApiService {
|
||||||
getAllPosts(): Promise<PostInfoResponseDto[]>;
|
getAllPosts(searchParams: PostListQueryDto): Promise<PostInfoResponseDto[]>;
|
||||||
getPost(id: string): Promise<PostResponseDto | null>;
|
getPost(id: string): Promise<PostResponseDto | null>;
|
||||||
createPost(payload: CreatePostRequestDto): Promise<PostResponseDto>;
|
createPost(payload: CreatePostRequestDto): Promise<PostResponseDto>;
|
||||||
}
|
}
|
||||||
|
13
frontend/src/lib/post/adapter/gateway/postListQueryDto.ts
Normal file
13
frontend/src/lib/post/adapter/gateway/postListQueryDto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class PostListQueryDto {
|
||||||
|
readonly showUnpublished: boolean;
|
||||||
|
|
||||||
|
constructor(props: { showUnpublished: boolean }) {
|
||||||
|
this.showUnpublished = props.showUnpublished;
|
||||||
|
}
|
||||||
|
|
||||||
|
toSearchParams(): URLSearchParams {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('is_published_only', (!this.showUnpublished).toString());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
import { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
||||||
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
|
import { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto';
|
||||||
import type {
|
import type {
|
||||||
CreatePostParams,
|
CreatePostParams,
|
||||||
PostRepository,
|
PostRepository,
|
||||||
@ -10,9 +11,10 @@ import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
|||||||
export class PostRepositoryImpl implements PostRepository {
|
export class PostRepositoryImpl implements PostRepository {
|
||||||
constructor(private readonly postApiService: PostApiService) {}
|
constructor(private readonly postApiService: PostApiService) {}
|
||||||
|
|
||||||
async getAllPosts(): Promise<PostInfo[]> {
|
async getAllPosts(showUnpublished: boolean): Promise<PostInfo[]> {
|
||||||
const dtos = await this.postApiService.getAllPosts();
|
const queryDto = new PostListQueryDto({ showUnpublished });
|
||||||
return dtos.map((dto) => dto.toEntity());
|
const responseDtos = await this.postApiService.getAllPosts(queryDto);
|
||||||
|
return responseDtos.map((dto) => dto.toEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPost(id: string): Promise<Post | null> {
|
async getPost(id: string): Promise<Post | null> {
|
||||||
|
@ -5,7 +5,7 @@ import type { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCas
|
|||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type PostState = AsyncState<PostViewModel>;
|
type PostState = AsyncState<PostViewModel>;
|
||||||
|
|
||||||
export class PostLoadedStore implements BaseStore<PostState, string> {
|
export class PostLoadedStore implements BaseStore<PostState, string> {
|
||||||
private readonly state = writable<PostState>(AsyncState.idle<PostViewModel>(null));
|
private readonly state = writable<PostState>(AsyncState.idle<PostViewModel>(null));
|
||||||
|
@ -5,9 +5,9 @@ import type { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPos
|
|||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
||||||
|
|
||||||
export class PostsListedStore implements BaseStore<PostListState> {
|
export class PostsListedStore implements BaseStore<PostListState, { showUnpublished: boolean }> {
|
||||||
private readonly state = writable<PostListState>(AsyncState.idle([]));
|
private readonly state = writable<PostListState>(AsyncState.idle([]));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -24,15 +24,15 @@ export class PostsListedStore implements BaseStore<PostListState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get trigger() {
|
get trigger() {
|
||||||
return () => this.loadPosts();
|
return (options?: { showUnpublished: boolean }) => this.loadPosts(options?.showUnpublished);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPosts(): Promise<PostListState> {
|
private async loadPosts(showUnpublished?: boolean): Promise<PostListState> {
|
||||||
this.state.set(AsyncState.loading(get(this.state).data));
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
let result: PostListState;
|
let result: PostListState;
|
||||||
try {
|
try {
|
||||||
const posts = await this.getAllPostsUseCase.execute();
|
const posts = await this.getAllPostsUseCase.execute(showUnpublished);
|
||||||
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
|
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
|
||||||
result = AsyncState.success(postViewModels);
|
result = AsyncState.success(postViewModels);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -2,7 +2,7 @@ import type { Post } from '$lib/post/domain/entity/post';
|
|||||||
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
export interface PostRepository {
|
export interface PostRepository {
|
||||||
getAllPosts(): Promise<PostInfo[]>;
|
getAllPosts(showUnpublished: boolean): Promise<PostInfo[]>;
|
||||||
getPost(id: string): Promise<Post | null>;
|
getPost(id: string): Promise<Post | null>;
|
||||||
createPost(params: CreatePostParams): Promise<Post>;
|
createPost(params: CreatePostParams): Promise<Post>;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
|||||||
export class GetAllPostsUseCase {
|
export class GetAllPostsUseCase {
|
||||||
constructor(private readonly postRepository: PostRepository) {}
|
constructor(private readonly postRepository: PostRepository) {}
|
||||||
|
|
||||||
execute(): Promise<PostInfo[]> {
|
execute(showUnpublished: boolean = false): Promise<PostInfo[]> {
|
||||||
return this.postRepository.getAllPosts();
|
return this.postRepository.getAllPosts(showUnpublished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,15 @@ import { Environment } from '$lib/environment';
|
|||||||
import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto';
|
||||||
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
import type { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto';
|
||||||
import { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
import { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
||||||
|
|
||||||
export class PostApiServiceImpl implements PostApiService {
|
export class PostApiServiceImpl implements PostApiService {
|
||||||
constructor(private readonly fetchFn: typeof fetch) {}
|
constructor(private readonly fetchFn: typeof fetch) {}
|
||||||
|
|
||||||
async getAllPosts(): Promise<PostInfoResponseDto[]> {
|
async getAllPosts(searchParams: PostListQueryDto): Promise<PostInfoResponseDto[]> {
|
||||||
const url = new URL('post', Environment.API_BASE_URL);
|
const url = new URL('post', Environment.API_BASE_URL);
|
||||||
|
url.search = searchParams.toSearchParams().toString();
|
||||||
|
|
||||||
const response = await this.fetchFn(url);
|
const response = await this.fetchFn(url);
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<article class="content-container prose prose-gray pb-10">
|
<article class="content-container prose pb-10 prose-gray">
|
||||||
{#if postInfo}
|
{#if postInfo}
|
||||||
<PostContentHeader {postInfo} />
|
<PostContentHeader {postInfo} />
|
||||||
<div class="max-w-3xl">
|
<div class="max-w-3xl">
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import TableBody from '$lib/common/framework/components/ui/table/table-body.svelte';
|
||||||
|
import TableCell from '$lib/common/framework/components/ui/table/table-cell.svelte';
|
||||||
|
import TableHead from '$lib/common/framework/components/ui/table/table-head.svelte';
|
||||||
|
import TableHeader from '$lib/common/framework/components/ui/table/table-header.svelte';
|
||||||
|
import TableRow from '$lib/common/framework/components/ui/table/table-row.svelte';
|
||||||
|
import Table from '$lib/common/framework/components/ui/table/table.svelte';
|
||||||
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
||||||
|
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
|
||||||
import CreatePostDialog, {
|
import CreatePostDialog, {
|
||||||
type CreatePostDialogFormParams,
|
type CreatePostDialogFormParams,
|
||||||
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
|
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
|
||||||
import { getContext } from 'svelte';
|
import PostLabel from '$lib/post/framework/ui/PostLabel.svelte';
|
||||||
|
import { getContext, onMount } from 'svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
const store = getContext<PostCreatedStore>(PostCreatedStore.name);
|
const postCreatedStore = getContext<PostCreatedStore>(PostCreatedStore.name);
|
||||||
const state = $derived($store);
|
const postCreatedState = $derived($postCreatedStore);
|
||||||
const { trigger: createPost } = store;
|
const { trigger: createPost } = postCreatedStore;
|
||||||
|
|
||||||
|
const postsListedStore = getContext<PostsListedStore>(PostsListedStore.name);
|
||||||
|
const postsListedState = $derived($postsListedStore);
|
||||||
|
const { trigger: loadPosts } = postsListedStore;
|
||||||
|
|
||||||
async function onCreatePostDialogSubmit(params: CreatePostDialogFormParams) {
|
async function onCreatePostDialogSubmit(params: CreatePostDialogFormParams) {
|
||||||
const state = await createPost(params);
|
const state = await createPost(params);
|
||||||
@ -21,12 +33,39 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => loadPosts({ showUnpublished: true }));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="dashboard-container mb-10">
|
<div class="dashboard-container mb-10">
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<h1 class="py-16 text-5xl font-bold text-gray-800">Post</h1>
|
<h1 class="py-16 text-5xl font-bold text-gray-800">Post</h1>
|
||||||
<CreatePostDialog disabled={state.isLoading()} onSubmit={onCreatePostDialogSubmit} />
|
<CreatePostDialog disabled={postCreatedState.isLoading()} onSubmit={onCreatePostDialogSubmit} />
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>ID</TableHead>
|
||||||
|
<TableHead>Title</TableHead>
|
||||||
|
<TableHead>Labels</TableHead>
|
||||||
|
<TableHead>Published Time</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{#if postsListedState.isSuccess()}
|
||||||
|
{#each postsListedState.data as postInfo (postInfo.id)}
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{postInfo.id}</TableCell>
|
||||||
|
<TableCell>{postInfo.title}</TableCell>
|
||||||
|
<TableCell class="flex flex-row flex-wrap gap-2">
|
||||||
|
{#each postInfo.labels as label (label.id)}
|
||||||
|
<PostLabel {label} />
|
||||||
|
{/each}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{postInfo.formattedPublishedTime || '---'}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
{:else if !isAuthenticated}
|
{:else if !isAuthenticated}
|
||||||
<ErrorPage />
|
<ErrorPage />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="min-h-content-height grid grid-cols-[auto_1fr]">
|
<div class="grid min-h-content-height grid-cols-[auto_1fr]">
|
||||||
<DashboardNavbar {links} />
|
<DashboardNavbar {links} />
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/imageUploadedStore';
|
||||||
import ImageManagementPage from '$lib/image/framework/ui/ImageManagementPage.svelte';
|
import ImageManagementPage from '$lib/image/framework/ui/ImageManagementPage.svelte';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
|
|
||||||
|
13
frontend/src/routes/dashboard/post/+page.server.ts
Normal file
13
frontend/src/routes/dashboard/post/+page.server.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
const { container } = locals;
|
||||||
|
const store = container.createPostsListedStore();
|
||||||
|
const { trigger: loadPosts } = store;
|
||||||
|
|
||||||
|
const state = await loadPosts();
|
||||||
|
|
||||||
|
return {
|
||||||
|
dehydratedData: state.data?.map((post) => post.dehydrate()),
|
||||||
|
};
|
||||||
|
};
|
@ -1,12 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
||||||
|
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
|
||||||
import PostManagementPage from '$lib/post/framework/ui/PostManagementPage.svelte';
|
import PostManagementPage from '$lib/post/framework/ui/PostManagementPage.svelte';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
|
|
||||||
|
const { data }: PageProps = $props();
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
const store = container.createPostCreatedStore();
|
|
||||||
setContext(PostCreatedStore.name, store);
|
const postCreatedStore = container.createPostCreatedStore();
|
||||||
|
setContext(PostCreatedStore.name, postCreatedStore);
|
||||||
|
|
||||||
|
const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post));
|
||||||
|
const postsListedStore = container.createPostsListedStore(initialData);
|
||||||
|
setContext(PostsListedStore.name, postsListedStore);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PostManagementPage />
|
<PostManagementPage />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user